minor fix
This commit is contained in:
parent
4c51d5133c
commit
ba8401345b
@ -1,17 +0,0 @@
|
||||
FROM python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
gcc \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY requirements.txt .
|
||||
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY backend/ .
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
|
@ -1,15 +0,0 @@
|
||||
FROM node:18-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
|
||||
RUN npm ci --only=production
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN npm run build
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["npm", "start"]
|
@ -1,35 +0,0 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
backend:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.backend
|
||||
container_name: pickleglass-backend
|
||||
restart: always
|
||||
ports:
|
||||
- "8000:8000"
|
||||
environment:
|
||||
- DATABASE_URL=/app/data/pickleglass.db
|
||||
volumes:
|
||||
- ./backend:/app
|
||||
- ./data:/app/data
|
||||
|
||||
frontend:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.frontend
|
||||
container_name: pickleglass-frontend
|
||||
restart: always
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- NEXT_PUBLIC_API_URL=http://localhost:8000
|
||||
depends_on:
|
||||
- backend
|
||||
volumes:
|
||||
- .:/app
|
||||
- /app/node_modules
|
||||
|
||||
volumes:
|
||||
mongodb_data:
|
@ -346,9 +346,18 @@ export class ApiKeyHeader extends LitElement {
|
||||
const isValid = await this.validateApiKey(this.apiKey.trim());
|
||||
|
||||
if (isValid) {
|
||||
console.log('API key valid - starting slide out animation');
|
||||
console.log('API key valid - checking system permissions...');
|
||||
|
||||
const permissionResult = await this.checkAndRequestPermissions();
|
||||
|
||||
if (permissionResult.success) {
|
||||
console.log('All permissions granted - starting slide out animation');
|
||||
this.startSlideOutAnimation();
|
||||
this.validatedApiKey = this.apiKey.trim();
|
||||
} else {
|
||||
this.errorMessage = permissionResult.error || 'Permission setup required';
|
||||
console.log('Permission setup incomplete:', permissionResult);
|
||||
}
|
||||
} else {
|
||||
this.errorMessage = 'Invalid API key - please check and try again';
|
||||
console.log('API key validation failed');
|
||||
@ -398,6 +407,58 @@ export class ApiKeyHeader extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
async checkAndRequestPermissions() {
|
||||
if (!window.require) {
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
|
||||
try {
|
||||
const permissions = await ipcRenderer.invoke('check-system-permissions');
|
||||
console.log('[Permissions] Current status:', permissions);
|
||||
|
||||
if (!permissions.needsSetup) {
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
if (!permissions.microphone) {
|
||||
console.log('[Permissions] Requesting microphone permission...');
|
||||
const micResult = await ipcRenderer.invoke('request-microphone-permission');
|
||||
|
||||
if (!micResult.success) {
|
||||
console.log('[Permissions] Microphone permission denied');
|
||||
await ipcRenderer.invoke('open-system-preferences', 'microphone');
|
||||
return {
|
||||
success: false,
|
||||
error: 'Please grant microphone access in System Preferences'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (!permissions.screen) {
|
||||
console.log('[Permissions] Screen recording permission needed');
|
||||
await ipcRenderer.invoke('open-system-preferences', 'screen-recording');
|
||||
|
||||
this.errorMessage = 'Please grant screen recording permission and try again';
|
||||
this.requestUpdate();
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: 'Please grant screen recording access in System Preferences'
|
||||
};
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error('[Permissions] Error checking/requesting permissions:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: 'Failed to check permissions'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
startSlideOutAnimation() {
|
||||
this.classList.add('sliding-out');
|
||||
}
|
||||
|
@ -10,25 +10,25 @@ export class AppHeader extends LitElement {
|
||||
display: block;
|
||||
transform: translate3d(0, 0, 0);
|
||||
backface-visibility: hidden;
|
||||
transition: transform 0.25s cubic-bezier(0.23, 1, 0.32, 1), opacity 0.25s ease-out;
|
||||
transition: transform 0.2s cubic-bezier(0.23, 1, 0.32, 1), opacity 0.2s ease-out;
|
||||
will-change: transform, opacity;
|
||||
}
|
||||
|
||||
:host(.hiding) {
|
||||
animation: slideUp 0.45s cubic-bezier(0.55, 0.085, 0.68, 0.53) forwards;
|
||||
animation: slideUp 0.3s cubic-bezier(0.4, 0, 0.6, 1) forwards;
|
||||
}
|
||||
|
||||
:host(.showing) {
|
||||
animation: slideDown 0.5s cubic-bezier(0.25, 0.8, 0.25, 1) forwards;
|
||||
animation: slideDown 0.35s cubic-bezier(0.34, 1.56, 0.64, 1) forwards;
|
||||
}
|
||||
|
||||
:host(.sliding-in) {
|
||||
animation: fadeIn 0.25s ease-out forwards;
|
||||
will-change: opacity;
|
||||
animation: fadeIn 0.2s ease-out forwards;
|
||||
}
|
||||
|
||||
:host(.hidden) {
|
||||
opacity: 0;
|
||||
transform: translateY(-180%) scale(0.8);
|
||||
transform: translateY(-150%) scale(0.85);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@ -36,65 +36,50 @@ export class AppHeader extends LitElement {
|
||||
0% {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
filter: blur(0px) brightness(1);
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
||||
filter: blur(0px);
|
||||
}
|
||||
25% {
|
||||
opacity: 0.85;
|
||||
transform: translateY(-20%) scale(0.96);
|
||||
filter: blur(0px) brightness(0.95);
|
||||
box-shadow: 0 6px 28px rgba(0, 0, 0, 0.25);
|
||||
30% {
|
||||
opacity: 0.7;
|
||||
transform: translateY(-20%) scale(0.98);
|
||||
filter: blur(0.5px);
|
||||
}
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
transform: translateY(-60%) scale(0.9);
|
||||
filter: blur(1px) brightness(0.85);
|
||||
box-shadow: 0 3px 15px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
75% {
|
||||
opacity: 0.15;
|
||||
transform: translateY(-120%) scale(0.85);
|
||||
filter: blur(2px) brightness(0.75);
|
||||
box-shadow: 0 1px 8px rgba(0, 0, 0, 0.08);
|
||||
70% {
|
||||
opacity: 0.3;
|
||||
transform: translateY(-80%) scale(0.92);
|
||||
filter: blur(1.5px);
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: translateY(-180%) scale(0.8);
|
||||
filter: blur(3px) brightness(0.7);
|
||||
box-shadow: 0 0px 0px rgba(0, 0, 0, 0);
|
||||
transform: translateY(-150%) scale(0.85);
|
||||
filter: blur(2px);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideDown {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(-180%) scale(0.8);
|
||||
filter: blur(3px) brightness(0.7);
|
||||
box-shadow: 0 0px 0px rgba(0, 0, 0, 0);
|
||||
transform: translateY(-150%) scale(0.85);
|
||||
filter: blur(2px);
|
||||
}
|
||||
40% {
|
||||
opacity: 0.6;
|
||||
transform: translateY(-30%) scale(0.95);
|
||||
filter: blur(1px) brightness(0.9);
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
|
||||
30% {
|
||||
opacity: 0.5;
|
||||
transform: translateY(-50%) scale(0.92);
|
||||
filter: blur(1px);
|
||||
}
|
||||
70% {
|
||||
65% {
|
||||
opacity: 0.9;
|
||||
transform: translateY(-5%) scale(1.01);
|
||||
filter: blur(0.3px) brightness(1.02);
|
||||
box-shadow: 0 7px 28px rgba(0, 0, 0, 0.28);
|
||||
transform: translateY(-5%) scale(0.99);
|
||||
filter: blur(0.2px);
|
||||
}
|
||||
85% {
|
||||
opacity: 0.98;
|
||||
transform: translateY(1%) scale(0.995);
|
||||
filter: blur(0.1px) brightness(1.01);
|
||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.31);
|
||||
transform: translateY(2%) scale(1.005);
|
||||
filter: blur(0px);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
filter: blur(0px) brightness(1);
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
|
||||
filter: blur(0px);
|
||||
}
|
||||
}
|
||||
|
||||
@ -318,6 +303,7 @@ export class AppHeader extends LitElement {
|
||||
this.hasSlidIn = false;
|
||||
this.settingsHideTimer = null;
|
||||
this.isSessionActive = false;
|
||||
this.animationEndTimer = null;
|
||||
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
@ -388,7 +374,15 @@ export class AppHeader extends LitElement {
|
||||
}
|
||||
|
||||
toggleVisibility() {
|
||||
if (this.isAnimating) return;
|
||||
if (this.isAnimating) {
|
||||
console.log('[AppHeader] Animation already in progress, ignoring toggle');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.animationEndTimer) {
|
||||
clearTimeout(this.animationEndTimer);
|
||||
this.animationEndTimer = null;
|
||||
}
|
||||
|
||||
this.isAnimating = true;
|
||||
|
||||
@ -403,17 +397,34 @@ export class AppHeader extends LitElement {
|
||||
this.classList.remove('showing', 'hidden');
|
||||
this.classList.add('hiding');
|
||||
this.isVisible = false;
|
||||
|
||||
this.animationEndTimer = setTimeout(() => {
|
||||
if (this.classList.contains('hiding')) {
|
||||
this.handleAnimationEnd({ target: this });
|
||||
}
|
||||
}, 350);
|
||||
}
|
||||
|
||||
show() {
|
||||
this.classList.remove('hiding', 'hidden');
|
||||
this.classList.add('showing');
|
||||
this.isVisible = true;
|
||||
|
||||
this.animationEndTimer = setTimeout(() => {
|
||||
if (this.classList.contains('showing')) {
|
||||
this.handleAnimationEnd({ target: this });
|
||||
}
|
||||
}, 400);
|
||||
}
|
||||
|
||||
handleAnimationEnd(e) {
|
||||
if (e.target !== this) return;
|
||||
|
||||
if (this.animationEndTimer) {
|
||||
clearTimeout(this.animationEndTimer);
|
||||
this.animationEndTimer = null;
|
||||
}
|
||||
|
||||
this.isAnimating = false;
|
||||
|
||||
if (this.classList.contains('hiding')) {
|
||||
@ -434,7 +445,7 @@ export class AppHeader extends LitElement {
|
||||
} else if (this.classList.contains('sliding-in')) {
|
||||
this.classList.remove('sliding-in');
|
||||
this.hasSlidIn = true;
|
||||
console.log('AppHeader slide-in animation completed');
|
||||
console.log('[AppHeader] Slide-in animation completed');
|
||||
}
|
||||
}
|
||||
|
||||
@ -460,6 +471,11 @@ export class AppHeader extends LitElement {
|
||||
super.disconnectedCallback();
|
||||
this.removeEventListener('animationend', this.handleAnimationEnd);
|
||||
|
||||
if (this.animationEndTimer) {
|
||||
clearTimeout(this.animationEndTimer);
|
||||
this.animationEndTimer = null;
|
||||
}
|
||||
|
||||
if (window.require) {
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
ipcRenderer.removeAllListeners('toggle-header-visibility');
|
||||
|
@ -81,11 +81,31 @@ class HeaderTransitionManager {
|
||||
|
||||
if (error) {
|
||||
console.warn('[HeaderController] Login payload indicates verification failure. Proceeding to AppHeader UI only.');
|
||||
// Check permissions before transitioning
|
||||
const permissionResult = await this.checkPermissions();
|
||||
if (permissionResult.success) {
|
||||
this.transitionToAppHeader();
|
||||
} else {
|
||||
console.log('[HeaderController] Permissions not granted after login error');
|
||||
if (this.apiKeyHeader) {
|
||||
this.apiKeyHeader.errorMessage = permissionResult.error || 'Permission setup required';
|
||||
this.apiKeyHeader.requestUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[HeaderController] Sign-in failed', error);
|
||||
// Check permissions before transitioning
|
||||
const permissionResult = await this.checkPermissions();
|
||||
if (permissionResult.success) {
|
||||
this.transitionToAppHeader();
|
||||
} else {
|
||||
console.log('[HeaderController] Permissions not granted after sign-in failure');
|
||||
if (this.apiKeyHeader) {
|
||||
this.apiKeyHeader.errorMessage = permissionResult.error || 'Permission setup required';
|
||||
this.apiKeyHeader.requestUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -125,7 +145,17 @@ class HeaderTransitionManager {
|
||||
console.log('[HeaderController] Firebase sign-in successful via ID token');
|
||||
} else {
|
||||
console.warn('[HeaderController] No ID token received from deeplink, virtual key request may fail');
|
||||
// Check permissions before transitioning
|
||||
const permissionResult = await this.checkPermissions();
|
||||
if (permissionResult.success) {
|
||||
this.transitionToAppHeader();
|
||||
} else {
|
||||
console.log('[HeaderController] Permissions not granted after Firebase auth');
|
||||
if (this.apiKeyHeader) {
|
||||
this.apiKeyHeader.errorMessage = permissionResult.error || 'Permission setup required';
|
||||
this.apiKeyHeader.requestUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[HeaderController] Firebase auth failed:', error);
|
||||
@ -173,11 +203,25 @@ class HeaderTransitionManager {
|
||||
}
|
||||
|
||||
if (user) {
|
||||
console.log('[HeaderController] User is logged in, transitioning to AppHeader');
|
||||
console.log('[HeaderController] User is logged in, checking permissions...');
|
||||
const permissionResult = await this.checkPermissions();
|
||||
if (permissionResult.success) {
|
||||
this.transitionToAppHeader(!this.hasApiKey);
|
||||
} else {
|
||||
console.log('[HeaderController] Permissions not granted, staying on ApiKeyHeader');
|
||||
if (this.apiKeyHeader) {
|
||||
this.apiKeyHeader.errorMessage = permissionResult.error || 'Permission setup required';
|
||||
this.apiKeyHeader.requestUpdate();
|
||||
}
|
||||
}
|
||||
} else if (this.hasApiKey) {
|
||||
console.log('[HeaderController] No Firebase user but API key exists, showing AppHeader');
|
||||
console.log('[HeaderController] No Firebase user but API key exists, checking permissions...');
|
||||
const permissionResult = await this.checkPermissions();
|
||||
if (permissionResult.success) {
|
||||
this.transitionToAppHeader(false);
|
||||
} else {
|
||||
console.log('[HeaderController] Permissions not granted, staying on ApiKeyHeader');
|
||||
}
|
||||
} else {
|
||||
console.log('[HeaderController] No auth & no API key — showing ApiKeyHeader');
|
||||
this.transitionToApiKeyHeader();
|
||||
@ -185,7 +229,6 @@ class HeaderTransitionManager {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
notifyHeaderState(stateOverride) {
|
||||
const state = stateOverride || this.currentHeaderType || 'apikey';
|
||||
if (window.require) {
|
||||
@ -212,14 +255,27 @@ class HeaderTransitionManager {
|
||||
});
|
||||
|
||||
if (user || this.hasApiKey) {
|
||||
const permissionResult = await this.checkPermissions();
|
||||
|
||||
if (permissionResult.success) {
|
||||
await this._resizeForApp();
|
||||
this.ensureHeader('app');
|
||||
} else {
|
||||
await this._resizeForApiKey();
|
||||
this.ensureHeader('apikey');
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (this.apiKeyHeader) {
|
||||
this.apiKeyHeader.errorMessage = permissionResult.error || 'Permission setup required';
|
||||
this.apiKeyHeader.requestUpdate();
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
} else {
|
||||
await this._resizeForApiKey();
|
||||
this.ensureHeader('apikey');
|
||||
}
|
||||
}
|
||||
|
||||
async transitionToAppHeader(animate = true) {
|
||||
if (this.currentHeaderType === 'app') {
|
||||
@ -256,9 +312,16 @@ class HeaderTransitionManager {
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
async _resizeForApiKey() {
|
||||
if (!window.require) return;
|
||||
return window
|
||||
.require('electron')
|
||||
.ipcRenderer.invoke('resize-header-window', { width: 285, height: 220 })
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
async transitionToApiKeyHeader() {
|
||||
await window.require('electron')
|
||||
.ipcRenderer.invoke('resize-header-window', { width: 285, height: 220 });
|
||||
await this._resizeForApiKey();
|
||||
|
||||
if (this.currentHeaderType !== 'apikey') {
|
||||
this.ensureHeader('apikey');
|
||||
@ -266,6 +329,45 @@ class HeaderTransitionManager {
|
||||
|
||||
if (this.apiKeyHeader) this.apiKeyHeader.reset();
|
||||
}
|
||||
|
||||
async checkPermissions() {
|
||||
if (!window.require) {
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
const { ipcRenderer } = window.require('electron');
|
||||
|
||||
try {
|
||||
// Check permission status
|
||||
const permissions = await ipcRenderer.invoke('check-system-permissions');
|
||||
console.log('[HeaderController] Current permissions:', permissions);
|
||||
|
||||
if (!permissions.needsSetup) {
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
// If permissions are not set up, return false
|
||||
let errorMessage = '';
|
||||
if (!permissions.microphone && !permissions.screen) {
|
||||
errorMessage = 'Microphone and screen recording access required';
|
||||
} else if (!permissions.microphone) {
|
||||
errorMessage = 'Microphone access required';
|
||||
} else if (!permissions.screen) {
|
||||
errorMessage = 'Screen recording access required';
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: errorMessage
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('[HeaderController] Error checking permissions:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: 'Failed to check permissions'
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
|
@ -224,7 +224,7 @@ class WindowLayoutManager {
|
||||
|
||||
const PAD = 8;
|
||||
|
||||
/* ① 헤더 중심 X를 “디스플레이 기준 상대좌표”로 변환 */
|
||||
/* ① 헤더 중심 X를 "디스플레이 기준 상대좌표"로 변환 */
|
||||
const headerCenterXRel = headerBounds.x - workAreaX + headerBounds.width / 2;
|
||||
|
||||
let askBounds = askVisible ? ask.getBounds() : null;
|
||||
@ -418,6 +418,49 @@ class SmoothMovementManager {
|
||||
this.hiddenPosition = null;
|
||||
this.lastVisiblePosition = null;
|
||||
this.currentDisplayId = null;
|
||||
this.currentAnimationTimer = null;
|
||||
this.animationAbortController = null;
|
||||
this.animationFrameRate = 16; // ~60fps
|
||||
}
|
||||
|
||||
safeSetPosition(window, x, y) {
|
||||
if (!window || window.isDestroyed()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let safeX = Number.isFinite(x) ? Math.round(x) : 0;
|
||||
let safeY = Number.isFinite(y) ? Math.round(y) : 0;
|
||||
|
||||
if (Object.is(safeX, -0)) safeX = 0;
|
||||
if (Object.is(safeY, -0)) safeY = 0;
|
||||
|
||||
safeX = parseInt(safeX, 10);
|
||||
safeY = parseInt(safeY, 10);
|
||||
|
||||
if (!Number.isInteger(safeX) || !Number.isInteger(safeY)) {
|
||||
console.error('[Movement] Invalid position after conversion:', { x: safeX, y: safeY, originalX: x, originalY: y });
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
window.setPosition(safeX, safeY);
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error('[Movement] setPosition failed with values:', { x: safeX, y: safeY }, err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
cancelCurrentAnimation() {
|
||||
if (this.currentAnimationTimer) {
|
||||
clearTimeout(this.currentAnimationTimer);
|
||||
this.currentAnimationTimer = null;
|
||||
}
|
||||
if (this.animationAbortController) {
|
||||
this.animationAbortController.abort();
|
||||
this.animationAbortController = null;
|
||||
}
|
||||
this.isAnimating = false;
|
||||
}
|
||||
|
||||
moveToDisplay(displayId) {
|
||||
@ -456,50 +499,83 @@ class SmoothMovementManager {
|
||||
this.currentDisplayId = targetDisplay.id;
|
||||
}
|
||||
|
||||
hideToEdge(edge, callback) {
|
||||
hideToEdge(edge, callback, errorCallback) {
|
||||
const header = windowPool.get('header');
|
||||
if (!header || !header.isVisible() || this.isAnimating) return;
|
||||
if (!header || !header.isVisible()) {
|
||||
if (errorCallback) errorCallback(new Error('Header not available or not visible'));
|
||||
return;
|
||||
}
|
||||
// cancel current animation
|
||||
this.cancelCurrentAnimation();
|
||||
|
||||
console.log(`[Movement] Hiding to ${edge} edge`);
|
||||
|
||||
const currentBounds = header.getBounds();
|
||||
let currentBounds;
|
||||
try {
|
||||
currentBounds = header.getBounds();
|
||||
} catch (err) {
|
||||
console.error('[Movement] Failed to get header bounds:', err);
|
||||
if (errorCallback) errorCallback(err);
|
||||
return;
|
||||
}
|
||||
|
||||
this.lastVisiblePosition = { x: currentBounds.x, y: currentBounds.y };
|
||||
this.headerPosition = { x: currentBounds.x, y: currentBounds.y };
|
||||
|
||||
const display = getCurrentDisplay(header);
|
||||
const { width: screenWidth, height: screenHeight } = display.workAreaSize;
|
||||
const { x: workAreaX, y: workAreaY } = display.workArea;
|
||||
const headerBounds = header.getBounds();
|
||||
|
||||
let targetX = this.headerPosition.x;
|
||||
let targetY = this.headerPosition.y;
|
||||
|
||||
switch (edge) {
|
||||
case 'top':
|
||||
targetY = workAreaY - headerBounds.height - 20;
|
||||
targetY = workAreaY - currentBounds.height - 20;
|
||||
break;
|
||||
case 'bottom':
|
||||
targetY = workAreaY + screenHeight + 20;
|
||||
break;
|
||||
case 'left':
|
||||
targetX = workAreaX - headerBounds.width - 20;
|
||||
targetX = workAreaX - currentBounds.width - 20;
|
||||
break;
|
||||
case 'right':
|
||||
targetX = workAreaX + screenWidth + 20;
|
||||
break;
|
||||
}
|
||||
|
||||
// 대상 위치 유효성 검사
|
||||
if (!Number.isFinite(targetX) || !Number.isFinite(targetY)) {
|
||||
console.error('[Movement] Invalid target position:', { targetX, targetY });
|
||||
if (errorCallback) errorCallback(new Error('Invalid target position'));
|
||||
return;
|
||||
}
|
||||
|
||||
this.hiddenPosition = { x: targetX, y: targetY, edge };
|
||||
|
||||
// create AbortController
|
||||
this.animationAbortController = new AbortController();
|
||||
const signal = this.animationAbortController.signal;
|
||||
|
||||
this.isAnimating = true;
|
||||
const startX = this.headerPosition.x;
|
||||
const startY = this.headerPosition.y;
|
||||
const duration = 400;
|
||||
const duration = 300;
|
||||
const startTime = Date.now();
|
||||
|
||||
const animate = () => {
|
||||
if (!header || typeof header.setPosition !== 'function' || header.isDestroyed()) {
|
||||
// check aborted
|
||||
if (signal.aborted) {
|
||||
this.isAnimating = false;
|
||||
if (errorCallback) errorCallback(new Error('Animation aborted'));
|
||||
return;
|
||||
}
|
||||
|
||||
// check destroyed
|
||||
if (!header || header.isDestroyed()) {
|
||||
this.isAnimating = false;
|
||||
this.currentAnimationTimer = null;
|
||||
if (errorCallback) errorCallback(new Error('Window destroyed during animation'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -510,44 +586,33 @@ class SmoothMovementManager {
|
||||
const currentX = startX + (targetX - startX) * eased;
|
||||
const currentY = startY + (targetY - startY) * eased;
|
||||
|
||||
// Validate computed positions before using
|
||||
if (!Number.isFinite(currentX) || !Number.isFinite(currentY)) {
|
||||
console.error('[Movement] Invalid animation values for hide:', {
|
||||
currentX, currentY, progress, eased, startX, startY, targetX, targetY
|
||||
});
|
||||
this.isAnimating = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Safely call setPosition
|
||||
try {
|
||||
header.setPosition(Math.round(currentX), Math.round(currentY));
|
||||
} catch (err) {
|
||||
console.error('[Movement] Failed to set position:', err);
|
||||
// set position safe
|
||||
const success = this.safeSetPosition(header, currentX, currentY);
|
||||
if (!success) {
|
||||
this.isAnimating = false;
|
||||
this.currentAnimationTimer = null;
|
||||
if (errorCallback) errorCallback(new Error('Failed to set position'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (progress < 1) {
|
||||
setTimeout(animate, 8);
|
||||
this.currentAnimationTimer = setTimeout(animate, this.animationFrameRate);
|
||||
} else {
|
||||
this.headerPosition = { x: targetX, y: targetY };
|
||||
|
||||
if (Number.isFinite(targetX) && Number.isFinite(targetY)) {
|
||||
try {
|
||||
header.setPosition(Math.round(targetX), Math.round(targetY));
|
||||
} catch (err) {
|
||||
console.error('[Movement] Failed to set final position:', err);
|
||||
}
|
||||
}
|
||||
// set final position
|
||||
this.safeSetPosition(header, targetX, targetY);
|
||||
|
||||
this.isAnimating = false;
|
||||
this.currentAnimationTimer = null;
|
||||
this.animationAbortController = null;
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
if (typeof callback === 'function' && !signal.aborted) {
|
||||
try {
|
||||
callback();
|
||||
} catch (err) {
|
||||
console.error('[Movement] Callback error:', err);
|
||||
if (errorCallback) errorCallback(err);
|
||||
}
|
||||
}
|
||||
|
||||
@ -555,30 +620,62 @@ class SmoothMovementManager {
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
animate();
|
||||
} catch (err) {
|
||||
console.error('[Movement] Animation start error:', err);
|
||||
this.isAnimating = false;
|
||||
if (errorCallback) errorCallback(err);
|
||||
}
|
||||
}
|
||||
|
||||
showFromEdge(callback) {
|
||||
showFromEdge(callback, errorCallback) {
|
||||
const header = windowPool.get('header');
|
||||
if (!header || this.isAnimating || !this.hiddenPosition || !this.lastVisiblePosition) return;
|
||||
if (!header || !this.hiddenPosition || !this.lastVisiblePosition) {
|
||||
if (errorCallback) errorCallback(new Error('Cannot show - missing required data'));
|
||||
return;
|
||||
}
|
||||
|
||||
this.cancelCurrentAnimation();
|
||||
|
||||
console.log(`[Movement] Showing from ${this.hiddenPosition.edge} edge`);
|
||||
|
||||
header.setPosition(this.hiddenPosition.x, this.hiddenPosition.y);
|
||||
if (!this.safeSetPosition(header, this.hiddenPosition.x, this.hiddenPosition.y)) {
|
||||
if (errorCallback) errorCallback(new Error('Failed to set initial position'));
|
||||
return;
|
||||
}
|
||||
|
||||
this.headerPosition = { x: this.hiddenPosition.x, y: this.hiddenPosition.y };
|
||||
|
||||
const targetX = this.lastVisiblePosition.x;
|
||||
const targetY = this.lastVisiblePosition.y;
|
||||
|
||||
if (!Number.isFinite(targetX) || !Number.isFinite(targetY)) {
|
||||
console.error('[Movement] Invalid target position for show:', { targetX, targetY });
|
||||
if (errorCallback) errorCallback(new Error('Invalid target position for show'));
|
||||
return;
|
||||
}
|
||||
|
||||
this.animationAbortController = new AbortController();
|
||||
const signal = this.animationAbortController.signal;
|
||||
|
||||
this.isAnimating = true;
|
||||
const startX = this.headerPosition.x;
|
||||
const startY = this.headerPosition.y;
|
||||
const duration = 500;
|
||||
const duration = 400;
|
||||
const startTime = Date.now();
|
||||
|
||||
const animate = () => {
|
||||
if (signal.aborted) {
|
||||
this.isAnimating = false;
|
||||
if (errorCallback) errorCallback(new Error('Animation aborted'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!header || header.isDestroyed()) {
|
||||
this.isAnimating = false;
|
||||
this.currentAnimationTimer = null;
|
||||
if (errorCallback) errorCallback(new Error('Window destroyed during animation'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -592,34 +689,47 @@ class SmoothMovementManager {
|
||||
const currentX = startX + (targetX - startX) * eased;
|
||||
const currentY = startY + (targetY - startY) * eased;
|
||||
|
||||
if (!Number.isFinite(currentX) || !Number.isFinite(currentY)) {
|
||||
console.error('[Movement] Invalid animation values for show:', { currentX, currentY, progress, eased });
|
||||
const success = this.safeSetPosition(header, currentX, currentY);
|
||||
if (!success) {
|
||||
this.isAnimating = false;
|
||||
this.currentAnimationTimer = null;
|
||||
if (errorCallback) errorCallback(new Error('Failed to set position'));
|
||||
return;
|
||||
}
|
||||
|
||||
header.setPosition(Math.round(currentX), Math.round(currentY));
|
||||
|
||||
if (progress < 1) {
|
||||
setTimeout(animate, 8);
|
||||
this.currentAnimationTimer = setTimeout(animate, this.animationFrameRate);
|
||||
} else {
|
||||
this.headerPosition = { x: targetX, y: targetY };
|
||||
this.headerPosition = { x: targetX, y: targetY };
|
||||
if (Number.isFinite(targetX) && Number.isFinite(targetY)) {
|
||||
header.setPosition(Math.round(targetX), Math.round(targetY));
|
||||
}
|
||||
this.safeSetPosition(header, targetX, targetY);
|
||||
|
||||
this.isAnimating = false;
|
||||
this.currentAnimationTimer = null;
|
||||
this.animationAbortController = null;
|
||||
|
||||
this.hiddenPosition = null;
|
||||
this.lastVisiblePosition = null;
|
||||
|
||||
if (callback) callback();
|
||||
if (typeof callback === 'function' && !signal.aborted) {
|
||||
try {
|
||||
callback();
|
||||
} catch (err) {
|
||||
console.error('[Movement] Show callback error:', err);
|
||||
if (errorCallback) errorCallback(err);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[Movement] Show from edge completed`);
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
animate();
|
||||
} catch (err) {
|
||||
console.error('[Movement] Animation start error:', err);
|
||||
this.isAnimating = false;
|
||||
if (errorCallback) errorCallback(err);
|
||||
}
|
||||
}
|
||||
|
||||
moveStep(direction) {
|
||||
@ -682,6 +792,9 @@ class SmoothMovementManager {
|
||||
}
|
||||
|
||||
animateToPosition(header, targetX, targetY) {
|
||||
// cancel animation
|
||||
this.cancelCurrentAnimation();
|
||||
|
||||
this.isAnimating = true;
|
||||
|
||||
const startX = this.headerPosition.x;
|
||||
@ -694,9 +807,14 @@ class SmoothMovementManager {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
this.animationAbortController = new AbortController();
|
||||
const signal = this.animationAbortController.signal;
|
||||
|
||||
const animate = () => {
|
||||
if (!header || header.isDestroyed()) {
|
||||
if (signal.aborted || !header || header.isDestroyed()) {
|
||||
this.isAnimating = false;
|
||||
this.currentAnimationTimer = null;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -708,24 +826,24 @@ class SmoothMovementManager {
|
||||
const currentX = startX + (targetX - startX) * eased;
|
||||
const currentY = startY + (targetY - startY) * eased;
|
||||
|
||||
if (!Number.isFinite(currentX) || !Number.isFinite(currentY)) {
|
||||
console.error('[Movement] Invalid animation values:', { currentX, currentY, progress, eased });
|
||||
const success = this.safeSetPosition(header, currentX, currentY);
|
||||
if (!success) {
|
||||
this.isAnimating = false;
|
||||
this.currentAnimationTimer = null;
|
||||
return;
|
||||
}
|
||||
|
||||
header.setPosition(Math.round(currentX), Math.round(currentY));
|
||||
|
||||
if (progress < 1) {
|
||||
setTimeout(animate, 8);
|
||||
this.currentAnimationTimer = setTimeout(animate, this.animationFrameRate);
|
||||
} else {
|
||||
this.headerPosition = { x: targetX, y: targetY };
|
||||
if (Number.isFinite(targetX) && Number.isFinite(targetY)) {
|
||||
header.setPosition(Math.round(targetX), Math.round(targetY));
|
||||
} else {
|
||||
console.warn('[Movement] Final position invalid, skip setPosition:', { targetX, targetY });
|
||||
}
|
||||
|
||||
|
||||
this.safeSetPosition(header, targetX, targetY);
|
||||
|
||||
this.isAnimating = false;
|
||||
this.currentAnimationTimer = null;
|
||||
this.animationAbortController = null;
|
||||
|
||||
updateLayout();
|
||||
|
||||
@ -738,16 +856,23 @@ class SmoothMovementManager {
|
||||
|
||||
moveToEdge(direction) {
|
||||
const header = windowPool.get('header');
|
||||
if (!header || !header.isVisible() || this.isAnimating) return;
|
||||
if (!header || !header.isVisible()) return;
|
||||
this.cancelCurrentAnimation();
|
||||
|
||||
console.log(`[Movement] Move to edge: ${direction}`);
|
||||
|
||||
const display = getCurrentDisplay(header);
|
||||
const { width, height } = display.workAreaSize;
|
||||
const { x: workAreaX, y: workAreaY } = display.workArea;
|
||||
const headerBounds = header.getBounds();
|
||||
|
||||
const currentBounds = header.getBounds();
|
||||
let currentBounds;
|
||||
try {
|
||||
currentBounds = header.getBounds();
|
||||
} catch (err) {
|
||||
console.error('[Movement] Failed to get header bounds:', err);
|
||||
return;
|
||||
}
|
||||
|
||||
let targetX = currentBounds.x;
|
||||
let targetY = currentBounds.y;
|
||||
|
||||
@ -756,23 +881,26 @@ class SmoothMovementManager {
|
||||
targetX = workAreaX;
|
||||
break;
|
||||
case 'right':
|
||||
targetX = workAreaX + width - headerBounds.width;
|
||||
targetX = workAreaX + width - currentBounds.width;
|
||||
break;
|
||||
case 'up':
|
||||
targetY = workAreaY;
|
||||
break;
|
||||
case 'down':
|
||||
targetY = workAreaY + height - headerBounds.height;
|
||||
targetY = workAreaY + height - currentBounds.height;
|
||||
break;
|
||||
}
|
||||
|
||||
this.headerPosition = { x: currentBounds.x, y: currentBounds.y };
|
||||
|
||||
this.animationAbortController = new AbortController();
|
||||
const signal = this.animationAbortController.signal;
|
||||
|
||||
this.isAnimating = true;
|
||||
const startX = this.headerPosition.x;
|
||||
const startY = this.headerPosition.y;
|
||||
const duration = 400;
|
||||
const startTime = Date.now(); // 이 줄을 animate 함수 정의 전으로 이동
|
||||
const duration = 350;
|
||||
const startTime = Date.now();
|
||||
|
||||
if (!Number.isFinite(targetX) || !Number.isFinite(targetY) || !Number.isFinite(startX) || !Number.isFinite(startY)) {
|
||||
console.error('[Movement] Invalid edge position values:', { startX, startY, targetX, targetY });
|
||||
@ -781,8 +909,9 @@ class SmoothMovementManager {
|
||||
}
|
||||
|
||||
const animate = () => {
|
||||
if (!header || header.isDestroyed()) {
|
||||
if (signal.aborted || !header || header.isDestroyed()) {
|
||||
this.isAnimating = false;
|
||||
this.currentAnimationTimer = null;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -794,22 +923,24 @@ class SmoothMovementManager {
|
||||
const currentX = startX + (targetX - startX) * eased;
|
||||
const currentY = startY + (targetY - startY) * eased;
|
||||
|
||||
if (!Number.isFinite(currentX) || !Number.isFinite(currentY)) {
|
||||
console.error('[Movement] Invalid edge animation values:', { currentX, currentY, progress, eased });
|
||||
|
||||
const success = this.safeSetPosition(header, currentX, currentY);
|
||||
if (!success) {
|
||||
this.isAnimating = false;
|
||||
this.currentAnimationTimer = null;
|
||||
return;
|
||||
}
|
||||
|
||||
header.setPosition(Math.round(currentX), Math.round(currentY));
|
||||
|
||||
if (progress < 1) {
|
||||
setTimeout(animate, 8);
|
||||
this.currentAnimationTimer = setTimeout(animate, this.animationFrameRate);
|
||||
} else {
|
||||
if (Number.isFinite(targetX) && Number.isFinite(targetY)) {
|
||||
header.setPosition(Math.round(targetX), Math.round(targetY));
|
||||
}
|
||||
|
||||
this.safeSetPosition(header, targetX, targetY);
|
||||
|
||||
this.headerPosition = { x: targetX, y: targetY };
|
||||
this.isAnimating = false;
|
||||
this.currentAnimationTimer = null;
|
||||
this.animationAbortController = null;
|
||||
|
||||
updateLayout();
|
||||
|
||||
@ -829,6 +960,7 @@ class SmoothMovementManager {
|
||||
}
|
||||
|
||||
destroy() {
|
||||
this.cancelCurrentAnimation();
|
||||
this.isAnimating = false;
|
||||
console.log('[Movement] Destroyed');
|
||||
}
|
||||
@ -837,19 +969,117 @@ class SmoothMovementManager {
|
||||
const layoutManager = new WindowLayoutManager();
|
||||
let movementManager = null;
|
||||
|
||||
function toggleAllWindowsVisibility() {
|
||||
const header = windowPool.get('header');
|
||||
if (!header) return;
|
||||
function isWindowSafe(window) {
|
||||
return window && !window.isDestroyed() && typeof window.getBounds === 'function';
|
||||
}
|
||||
|
||||
function safeWindowOperation(window, operation, fallback = null) {
|
||||
if (!isWindowSafe(window)) {
|
||||
console.warn('[WindowManager] Window not safe for operation');
|
||||
return fallback;
|
||||
}
|
||||
|
||||
try {
|
||||
return operation(window);
|
||||
} catch (error) {
|
||||
console.error('[WindowManager] Window operation failed:', error);
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
function safeSetPosition(window, x, y) {
|
||||
return safeWindowOperation(window, (win) => {
|
||||
win.setPosition(Math.round(x), Math.round(y));
|
||||
return true;
|
||||
}, false);
|
||||
}
|
||||
|
||||
function safeGetBounds(window) {
|
||||
return safeWindowOperation(window, (win) => win.getBounds(), null);
|
||||
}
|
||||
|
||||
function safeShow(window) {
|
||||
return safeWindowOperation(window, (win) => {
|
||||
win.show();
|
||||
return true;
|
||||
}, false);
|
||||
}
|
||||
|
||||
function safeHide(window) {
|
||||
return safeWindowOperation(window, (win) => {
|
||||
win.hide();
|
||||
return true;
|
||||
}, false);
|
||||
}
|
||||
|
||||
let toggleState = {
|
||||
isToggling: false,
|
||||
lastToggleTime: 0,
|
||||
pendingToggle: null,
|
||||
toggleDebounceTimer: null,
|
||||
failsafeTimer: null
|
||||
};
|
||||
|
||||
function toggleAllWindowsVisibility() {
|
||||
const now = Date.now();
|
||||
const timeSinceLastToggle = now - toggleState.lastToggleTime;
|
||||
|
||||
if (timeSinceLastToggle < 200) {
|
||||
console.log('[Visibility] Toggle ignored - too fast (debounced)');
|
||||
return;
|
||||
}
|
||||
if (toggleState.isToggling) {
|
||||
console.log('[Visibility] Toggle in progress, queueing request');
|
||||
|
||||
if (toggleState.toggleDebounceTimer) {
|
||||
clearTimeout(toggleState.toggleDebounceTimer);
|
||||
}
|
||||
|
||||
toggleState.toggleDebounceTimer = setTimeout(() => {
|
||||
toggleState.toggleDebounceTimer = null;
|
||||
if (!toggleState.isToggling) {
|
||||
toggleAllWindowsVisibility();
|
||||
}
|
||||
}, 300);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const header = windowPool.get('header');
|
||||
if (!header || header.isDestroyed()) {
|
||||
console.error('[Visibility] Header window not found or destroyed');
|
||||
return;
|
||||
}
|
||||
|
||||
toggleState.isToggling = true;
|
||||
toggleState.lastToggleTime = now;
|
||||
const resetToggleState = () => {
|
||||
toggleState.isToggling = false;
|
||||
if (toggleState.toggleDebounceTimer) {
|
||||
clearTimeout(toggleState.toggleDebounceTimer);
|
||||
toggleState.toggleDebounceTimer = null;
|
||||
}
|
||||
if (toggleState.failsafeTimer) {
|
||||
clearTimeout(toggleState.failsafeTimer);
|
||||
toggleState.failsafeTimer = null;
|
||||
}
|
||||
};
|
||||
toggleState.failsafeTimer = setTimeout(() => {
|
||||
console.warn('[Visibility] Toggle operation timed out, resetting state');
|
||||
resetToggleState();
|
||||
}, 2000);
|
||||
|
||||
try {
|
||||
if (header.isVisible()) {
|
||||
console.log('[Visibility] Smart hiding - calculating nearest edge');
|
||||
|
||||
const headerBounds = header.getBounds();
|
||||
const display = screen.getPrimaryDisplay();
|
||||
const display = getCurrentDisplay(header);
|
||||
const { width: screenWidth, height: screenHeight } = display.workAreaSize;
|
||||
const { x: workAreaX, y: workAreaY } = display.workArea;
|
||||
|
||||
const centerX = headerBounds.x + headerBounds.width / 2;
|
||||
const centerY = headerBounds.y + headerBounds.height / 2;
|
||||
const centerX = headerBounds.x + headerBounds.width / 2 - workAreaX;
|
||||
const centerY = headerBounds.y + headerBounds.height / 2 - workAreaY;
|
||||
|
||||
const distances = {
|
||||
top: centerY,
|
||||
@ -858,54 +1088,100 @@ function toggleAllWindowsVisibility() {
|
||||
right: screenWidth - centerX,
|
||||
};
|
||||
|
||||
const nearestEdge = Object.keys(distances).reduce((nearest, edge) => (distances[edge] < distances[nearest] ? edge : nearest));
|
||||
const nearestEdge = Object.keys(distances).reduce((nearest, edge) =>
|
||||
(distances[edge] < distances[nearest] ? edge : nearest)
|
||||
);
|
||||
|
||||
console.log(`[Visibility] Nearest edge: ${nearestEdge} (distance: ${distances[nearestEdge].toFixed(1)}px)`);
|
||||
|
||||
lastVisibleWindows.clear();
|
||||
lastVisibleWindows.add('header');
|
||||
|
||||
const hidePromises = [];
|
||||
windowPool.forEach((win, name) => {
|
||||
if (win.isVisible()) {
|
||||
if (win && !win.isDestroyed() && win.isVisible() && name !== 'header') {
|
||||
lastVisibleWindows.add(name);
|
||||
if (name !== 'header') {
|
||||
|
||||
win.webContents.send('window-hide-animation');
|
||||
|
||||
hidePromises.push(new Promise(resolve => {
|
||||
setTimeout(() => {
|
||||
if (!win.isDestroyed()) {
|
||||
win.hide();
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
resolve();
|
||||
}, 180); // 200ms ->180ms
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
console.log('[Visibility] Visible windows before hide:', Array.from(lastVisibleWindows));
|
||||
|
||||
Promise.all(hidePromises).then(() => {
|
||||
if (!movementManager || header.isDestroyed()) {
|
||||
resetToggleState();
|
||||
return;
|
||||
}
|
||||
|
||||
movementManager.hideToEdge(nearestEdge, () => {
|
||||
if (!header.isDestroyed()) {
|
||||
header.hide();
|
||||
}
|
||||
resetToggleState();
|
||||
console.log('[Visibility] Smart hide completed');
|
||||
}, (error) => {
|
||||
console.error('[Visibility] Error in hideToEdge:', error);
|
||||
resetToggleState();
|
||||
});
|
||||
}).catch(err => {
|
||||
console.error('[Visibility] Error during hide:', err);
|
||||
resetToggleState();
|
||||
});
|
||||
|
||||
} else {
|
||||
console.log('[Visibility] Smart showing from hidden position');
|
||||
console.log('[Visibility] Restoring windows:', Array.from(lastVisibleWindows));
|
||||
|
||||
header.show();
|
||||
|
||||
if (!movementManager) {
|
||||
console.error('[Visibility] Movement manager not initialized');
|
||||
resetToggleState();
|
||||
return;
|
||||
}
|
||||
|
||||
movementManager.showFromEdge(() => {
|
||||
const showPromises = [];
|
||||
lastVisibleWindows.forEach(name => {
|
||||
if (name === 'header') return;
|
||||
|
||||
const win = windowPool.get(name);
|
||||
if (win && !win.isDestroyed()) {
|
||||
showPromises.push(new Promise(resolve => {
|
||||
win.show();
|
||||
win.webContents.send('window-show-animation');
|
||||
setTimeout(resolve, 100);
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
Promise.all(showPromises).then(() => {
|
||||
setImmediate(updateLayout);
|
||||
setTimeout(updateLayout, 120);
|
||||
setTimeout(updateLayout, 100);
|
||||
|
||||
resetToggleState();
|
||||
console.log('[Visibility] Smart show completed');
|
||||
}).catch(err => {
|
||||
console.error('[Visibility] Error during show:', err);
|
||||
resetToggleState();
|
||||
});
|
||||
}, (error) => {
|
||||
console.error('[Visibility] Error in showFromEdge:', error);
|
||||
resetToggleState();
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[Visibility] Unexpected error in toggle:', error);
|
||||
resetToggleState();
|
||||
}
|
||||
}
|
||||
|
||||
@ -926,6 +1202,21 @@ function ensureDataDirectories() {
|
||||
}
|
||||
|
||||
function createWindows() {
|
||||
if (movementManager) {
|
||||
movementManager.destroy();
|
||||
movementManager = null;
|
||||
}
|
||||
|
||||
toggleState.isToggling = false;
|
||||
if (toggleState.toggleDebounceTimer) {
|
||||
clearTimeout(toggleState.toggleDebounceTimer);
|
||||
toggleState.toggleDebounceTimer = null;
|
||||
}
|
||||
if (toggleState.failsafeTimer) {
|
||||
clearTimeout(toggleState.failsafeTimer);
|
||||
toggleState.failsafeTimer = null;
|
||||
}
|
||||
|
||||
const primaryDisplay = screen.getPrimaryDisplay();
|
||||
const { y: workAreaY, width: screenWidth } = primaryDisplay.workArea;
|
||||
|
||||
@ -994,11 +1285,25 @@ function createWindows() {
|
||||
loadAndRegisterShortcuts();
|
||||
});
|
||||
|
||||
ipcMain.handle('toggle-all-windows-visibility', toggleAllWindowsVisibility);
|
||||
ipcMain.handle('toggle-all-windows-visibility', () => {
|
||||
try {
|
||||
toggleAllWindowsVisibility();
|
||||
} catch (error) {
|
||||
console.error('[WindowManager] Error in toggle-all-windows-visibility:', error);
|
||||
toggleState.isToggling = false;
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('toggle-feature', async (event, featureName) => {
|
||||
try {
|
||||
const header = windowPool.get('header');
|
||||
if (!header || header.isDestroyed()) {
|
||||
console.error('[WindowManager] Header window not available');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!windowPool.get(featureName) && currentHeaderState === 'app') {
|
||||
createFeatureWindows(windowPool.get('header'));
|
||||
createFeatureWindows(header);
|
||||
}
|
||||
|
||||
if (!windowPool.get(featureName) && currentHeaderState === 'app') {
|
||||
@ -1142,6 +1447,10 @@ function createWindows() {
|
||||
console.error('Available windows:', Array.from(windowPool.keys()));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[WindowManager] Error in toggle-feature:', error);
|
||||
toggleState.isToggling = false;
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('send-question-to-ask', (event, question) => {
|
||||
@ -1228,13 +1537,51 @@ function loadAndRegisterShortcuts() {
|
||||
}
|
||||
|
||||
function updateLayout() {
|
||||
if (layoutManager._updateTimer) {
|
||||
clearTimeout(layoutManager._updateTimer);
|
||||
}
|
||||
|
||||
layoutManager._updateTimer = setTimeout(() => {
|
||||
layoutManager._updateTimer = null;
|
||||
layoutManager.updateLayout();
|
||||
}, 16);
|
||||
}
|
||||
|
||||
function setupIpcHandlers(openaiSessionRef) {
|
||||
const layoutManager = new WindowLayoutManager();
|
||||
// const movementManager = new SmoothMovementManager();
|
||||
|
||||
//cleanup
|
||||
app.on('before-quit', () => {
|
||||
console.log('[WindowManager] App is quitting, cleaning up...');
|
||||
|
||||
if (movementManager) {
|
||||
movementManager.destroy();
|
||||
}
|
||||
|
||||
if (toggleState.toggleDebounceTimer) {
|
||||
clearTimeout(toggleState.toggleDebounceTimer);
|
||||
toggleState.toggleDebounceTimer = null;
|
||||
}
|
||||
|
||||
if (toggleState.failsafeTimer) {
|
||||
clearTimeout(toggleState.failsafeTimer);
|
||||
toggleState.failsafeTimer = null;
|
||||
}
|
||||
|
||||
if (settingsHideTimer) {
|
||||
clearTimeout(settingsHideTimer);
|
||||
settingsHideTimer = null;
|
||||
}
|
||||
|
||||
windowPool.forEach((win, name) => {
|
||||
if (win && !win.isDestroyed()) {
|
||||
win.destroy();
|
||||
}
|
||||
});
|
||||
windowPool.clear();
|
||||
});
|
||||
|
||||
screen.on('display-added', (event, newDisplay) => {
|
||||
console.log('[Display] New display added:', newDisplay.id);
|
||||
});
|
||||
@ -1823,6 +2170,99 @@ function setupIpcHandlers(openaiSessionRef) {
|
||||
header.webContents.send('request-firebase-logout');
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('check-system-permissions', async () => {
|
||||
const { systemPreferences } = require('electron');
|
||||
const permissions = {
|
||||
microphone: false,
|
||||
screen: false,
|
||||
needsSetup: false
|
||||
};
|
||||
|
||||
try {
|
||||
if (process.platform === 'darwin') {
|
||||
// Check microphone permission on macOS
|
||||
const micStatus = systemPreferences.getMediaAccessStatus('microphone');
|
||||
permissions.microphone = micStatus === 'granted';
|
||||
|
||||
try {
|
||||
const sources = await desktopCapturer.getSources({
|
||||
types: ['screen'],
|
||||
thumbnailSize: { width: 1, height: 1 }
|
||||
});
|
||||
permissions.screen = sources && sources.length > 0;
|
||||
} catch (err) {
|
||||
console.log('[Permissions] Screen capture test failed:', err);
|
||||
permissions.screen = false;
|
||||
}
|
||||
|
||||
permissions.needsSetup = !permissions.microphone || !permissions.screen;
|
||||
} else {
|
||||
permissions.microphone = true;
|
||||
permissions.screen = true;
|
||||
permissions.needsSetup = false;
|
||||
}
|
||||
|
||||
console.log('[Permissions] System permissions status:', permissions);
|
||||
return permissions;
|
||||
} catch (error) {
|
||||
console.error('[Permissions] Error checking permissions:', error);
|
||||
return {
|
||||
microphone: false,
|
||||
screen: false,
|
||||
needsSetup: true,
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('request-microphone-permission', async () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
const { systemPreferences } = require('electron');
|
||||
try {
|
||||
const status = systemPreferences.getMediaAccessStatus('microphone');
|
||||
if (status === 'granted') {
|
||||
return { success: true, status: 'already-granted' };
|
||||
}
|
||||
|
||||
// Req mic permission
|
||||
const granted = await systemPreferences.askForMediaAccess('microphone');
|
||||
return {
|
||||
success: granted,
|
||||
status: granted ? 'granted' : 'denied'
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('[Permissions] Error requesting microphone permission:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('open-system-preferences', async (event, section) => {
|
||||
if (process.platform !== 'darwin') {
|
||||
return { success: false, error: 'Not supported on this platform' };
|
||||
}
|
||||
|
||||
try {
|
||||
// Open System Preferences to Privacy & Security > Screen Recording
|
||||
if (section === 'screen-recording') {
|
||||
await shell.openExternal('x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture');
|
||||
} else if (section === 'microphone') {
|
||||
await shell.openExternal('x-apple.systempreferences:com.apple.preference.security?Privacy_Microphone');
|
||||
} else {
|
||||
await shell.openExternal('x-apple.systempreferences:com.apple.preference.security?Privacy');
|
||||
}
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error('[Permissions] Error opening system preferences:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let storedApiKey = null;
|
||||
|
12
src/index.js
12
src/index.js
@ -1,9 +1,9 @@
|
||||
try {
|
||||
const reloader = require('electron-reloader');
|
||||
reloader(module, {
|
||||
});
|
||||
} catch (err) {
|
||||
}
|
||||
// try {
|
||||
// const reloader = require('electron-reloader');
|
||||
// reloader(module, {
|
||||
// });
|
||||
// } catch (err) {
|
||||
// }
|
||||
|
||||
require('dotenv').config();
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user