refactored layoutmanager, movementmanager
This commit is contained in:
parent
f755fdb9e3
commit
ecae4050bb
@ -19,9 +19,10 @@ function getDisplayById(displayId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class SmoothMovementManager {
|
class SmoothMovementManager {
|
||||||
constructor(windowPool, layoutManager) {
|
// constructor(windowPool, layoutManager) {
|
||||||
|
constructor(windowPool) {
|
||||||
this.windowPool = windowPool;
|
this.windowPool = windowPool;
|
||||||
this.layoutManager = layoutManager;
|
// this.layoutManager = layoutManager;
|
||||||
this.stepSize = 80;
|
this.stepSize = 80;
|
||||||
this.animationDuration = 300;
|
this.animationDuration = 300;
|
||||||
this.headerPosition = { x: 0, y: 0 };
|
this.headerPosition = { x: 0, y: 0 };
|
||||||
@ -51,90 +52,90 @@ class SmoothMovementManager {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
moveToDisplay(displayId) {
|
// moveToDisplay(displayId) {
|
||||||
const header = this.windowPool.get('header');
|
// const header = this.windowPool.get('header');
|
||||||
if (!this._isWindowValid(header) || !header.isVisible() || this.isAnimating) return;
|
// if (!this._isWindowValid(header) || !header.isVisible() || this.isAnimating) return;
|
||||||
|
|
||||||
const targetDisplay = getDisplayById(displayId);
|
// const targetDisplay = getDisplayById(displayId);
|
||||||
if (!targetDisplay) return;
|
// if (!targetDisplay) return;
|
||||||
|
|
||||||
const currentBounds = header.getBounds();
|
// const currentBounds = header.getBounds();
|
||||||
const currentDisplay = getCurrentDisplay(header);
|
// const currentDisplay = getCurrentDisplay(header);
|
||||||
|
|
||||||
if (currentDisplay.id === targetDisplay.id) return;
|
// if (currentDisplay.id === targetDisplay.id) return;
|
||||||
|
|
||||||
const relativeX = (currentBounds.x - currentDisplay.workArea.x) / currentDisplay.workAreaSize.width;
|
// const relativeX = (currentBounds.x - currentDisplay.workArea.x) / currentDisplay.workAreaSize.width;
|
||||||
const relativeY = (currentBounds.y - currentDisplay.workArea.y) / currentDisplay.workAreaSize.height;
|
// const relativeY = (currentBounds.y - currentDisplay.workArea.y) / currentDisplay.workAreaSize.height;
|
||||||
const targetX = targetDisplay.workArea.x + targetDisplay.workAreaSize.width * relativeX;
|
// const targetX = targetDisplay.workArea.x + targetDisplay.workAreaSize.width * relativeX;
|
||||||
const targetY = targetDisplay.workArea.y + targetDisplay.workAreaSize.height * relativeY;
|
// const targetY = targetDisplay.workArea.y + targetDisplay.workAreaSize.height * relativeY;
|
||||||
|
|
||||||
const finalX = Math.max(targetDisplay.workArea.x, Math.min(targetDisplay.workArea.x + targetDisplay.workAreaSize.width - currentBounds.width, targetX));
|
// const finalX = Math.max(targetDisplay.workArea.x, Math.min(targetDisplay.workArea.x + targetDisplay.workAreaSize.width - currentBounds.width, targetX));
|
||||||
const finalY = Math.max(targetDisplay.workArea.y, Math.min(targetDisplay.workArea.y + targetDisplay.workAreaSize.height - currentBounds.height, targetY));
|
// const finalY = Math.max(targetDisplay.workArea.y, Math.min(targetDisplay.workArea.y + targetDisplay.workAreaSize.height - currentBounds.height, targetY));
|
||||||
|
|
||||||
this.headerPosition = { x: currentBounds.x, y: currentBounds.y };
|
// this.headerPosition = { x: currentBounds.x, y: currentBounds.y };
|
||||||
this.animateToPosition(header, finalX, finalY);
|
// this.animateToPosition(header, finalX, finalY);
|
||||||
this.currentDisplayId = targetDisplay.id;
|
// this.currentDisplayId = targetDisplay.id;
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
|
||||||
moveStep(direction) {
|
// moveStep(direction) {
|
||||||
const header = this.windowPool.get('header');
|
// const header = this.windowPool.get('header');
|
||||||
if (!this._isWindowValid(header) || !header.isVisible() || this.isAnimating) return;
|
// if (!this._isWindowValid(header) || !header.isVisible() || this.isAnimating) return;
|
||||||
|
|
||||||
const currentBounds = header.getBounds();
|
// const currentBounds = header.getBounds();
|
||||||
this.headerPosition = { x: currentBounds.x, y: currentBounds.y };
|
// this.headerPosition = { x: currentBounds.x, y: currentBounds.y };
|
||||||
let targetX = this.headerPosition.x;
|
// let targetX = this.headerPosition.x;
|
||||||
let targetY = this.headerPosition.y;
|
// let targetY = this.headerPosition.y;
|
||||||
|
|
||||||
console.log(`[MovementManager] Moving ${direction} from (${targetX}, ${targetY})`);
|
// console.log(`[MovementManager] Moving ${direction} from (${targetX}, ${targetY})`);
|
||||||
|
|
||||||
const windowSize = {
|
// const windowSize = {
|
||||||
width: currentBounds.width,
|
// width: currentBounds.width,
|
||||||
height: currentBounds.height
|
// height: currentBounds.height
|
||||||
};
|
// };
|
||||||
|
|
||||||
switch (direction) {
|
// switch (direction) {
|
||||||
case 'left': targetX -= this.stepSize; break;
|
// case 'left': targetX -= this.stepSize; break;
|
||||||
case 'right': targetX += this.stepSize; break;
|
// case 'right': targetX += this.stepSize; break;
|
||||||
case 'up': targetY -= this.stepSize; break;
|
// case 'up': targetY -= this.stepSize; break;
|
||||||
case 'down': targetY += this.stepSize; break;
|
// case 'down': targetY += this.stepSize; break;
|
||||||
default: return;
|
// default: return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Find the display that contains or is nearest to the target position
|
// // Find the display that contains or is nearest to the target position
|
||||||
const nearestDisplay = screen.getDisplayNearestPoint({ x: targetX, y: targetY });
|
// const nearestDisplay = screen.getDisplayNearestPoint({ x: targetX, y: targetY });
|
||||||
const { x: workAreaX, y: workAreaY, width: workAreaWidth, height: workAreaHeight } = nearestDisplay.workArea;
|
// const { x: workAreaX, y: workAreaY, width: workAreaWidth, height: workAreaHeight } = nearestDisplay.workArea;
|
||||||
|
|
||||||
// Only clamp if the target position would actually go out of bounds
|
// // Only clamp if the target position would actually go out of bounds
|
||||||
let clampedX = targetX;
|
// let clampedX = targetX;
|
||||||
let clampedY = targetY;
|
// let clampedY = targetY;
|
||||||
|
|
||||||
// Check horizontal bounds
|
// // Check horizontal bounds
|
||||||
if (targetX < workAreaX) {
|
// if (targetX < workAreaX) {
|
||||||
clampedX = workAreaX;
|
// clampedX = workAreaX;
|
||||||
} else if (targetX + currentBounds.width > workAreaX + workAreaWidth) {
|
// } else if (targetX + currentBounds.width > workAreaX + workAreaWidth) {
|
||||||
clampedX = workAreaX + workAreaWidth - currentBounds.width;
|
// clampedX = workAreaX + workAreaWidth - currentBounds.width;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Check vertical bounds
|
// // Check vertical bounds
|
||||||
if (targetY < workAreaY) {
|
// if (targetY < workAreaY) {
|
||||||
clampedY = workAreaY;
|
// clampedY = workAreaY;
|
||||||
console.log(`[MovementManager] Clamped Y to top edge: ${clampedY}`);
|
// console.log(`[MovementManager] Clamped Y to top edge: ${clampedY}`);
|
||||||
} else if (targetY + currentBounds.height > workAreaY + workAreaHeight) {
|
// } else if (targetY + currentBounds.height > workAreaY + workAreaHeight) {
|
||||||
clampedY = workAreaY + workAreaHeight - currentBounds.height;
|
// clampedY = workAreaY + workAreaHeight - currentBounds.height;
|
||||||
console.log(`[MovementManager] Clamped Y to bottom edge: ${clampedY}`);
|
// console.log(`[MovementManager] Clamped Y to bottom edge: ${clampedY}`);
|
||||||
}
|
// }
|
||||||
|
|
||||||
console.log(`[MovementManager] Final position: (${clampedX}, ${clampedY}), Work area: ${workAreaX},${workAreaY} ${workAreaWidth}x${workAreaHeight}`);
|
// console.log(`[MovementManager] Final position: (${clampedX}, ${clampedY}), Work area: ${workAreaX},${workAreaY} ${workAreaWidth}x${workAreaHeight}`);
|
||||||
|
|
||||||
// Only move if there's an actual change in position
|
// // Only move if there's an actual change in position
|
||||||
if (clampedX === this.headerPosition.x && clampedY === this.headerPosition.y) {
|
// if (clampedX === this.headerPosition.x && clampedY === this.headerPosition.y) {
|
||||||
console.log(`[MovementManager] No position change, skipping animation`);
|
// console.log(`[MovementManager] No position change, skipping animation`);
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
this.animateToPosition(header, clampedX, clampedY, windowSize);
|
// this.animateToPosition(header, clampedX, clampedY, windowSize);
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [수정됨] 창을 목표 지점으로 부드럽게 애니메이션합니다.
|
* [수정됨] 창을 목표 지점으로 부드럽게 애니메이션합니다.
|
||||||
@ -186,100 +187,190 @@ class SmoothMovementManager {
|
|||||||
step();
|
step();
|
||||||
}
|
}
|
||||||
|
|
||||||
animateToPosition(header, targetX, targetY, windowSize) {
|
// animateToPosition(header, targetX, targetY, windowSize) {
|
||||||
if (!this._isWindowValid(header)) return;
|
// if (!this._isWindowValid(header)) return;
|
||||||
|
|
||||||
this.isAnimating = true;
|
// this.isAnimating = true;
|
||||||
const startX = this.headerPosition.x;
|
// const startX = this.headerPosition.x;
|
||||||
const startY = this.headerPosition.y;
|
// const startY = this.headerPosition.y;
|
||||||
|
// const startTime = Date.now();
|
||||||
|
|
||||||
|
// if (!Number.isFinite(targetX) || !Number.isFinite(targetY) || !Number.isFinite(startX) || !Number.isFinite(startY)) {
|
||||||
|
// this.isAnimating = false;
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const animate = () => {
|
||||||
|
// if (!this._isWindowValid(header)) return;
|
||||||
|
|
||||||
|
// const elapsed = Date.now() - startTime;
|
||||||
|
// const progress = Math.min(elapsed / this.animationDuration, 1);
|
||||||
|
// const eased = 1 - Math.pow(1 - progress, 3);
|
||||||
|
// const currentX = startX + (targetX - startX) * eased;
|
||||||
|
// const currentY = startY + (targetY - startY) * eased;
|
||||||
|
|
||||||
|
// if (!Number.isFinite(currentX) || !Number.isFinite(currentY)) {
|
||||||
|
// this.isAnimating = false;
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if (!this._isWindowValid(header)) return;
|
||||||
|
// const { width, height } = windowSize || header.getBounds();
|
||||||
|
// header.setBounds({
|
||||||
|
// x: Math.round(currentX),
|
||||||
|
// y: Math.round(currentY),
|
||||||
|
// width,
|
||||||
|
// height
|
||||||
|
// });
|
||||||
|
|
||||||
|
// if (progress < 1) {
|
||||||
|
// this.animationFrameId = setTimeout(animate, 8);
|
||||||
|
// } else {
|
||||||
|
// this.animationFrameId = null;
|
||||||
|
// this.isAnimating = false;
|
||||||
|
// if (Number.isFinite(targetX) && Number.isFinite(targetY)) {
|
||||||
|
// if (!this._isWindowValid(header)) return;
|
||||||
|
// header.setPosition(Math.round(targetX), Math.round(targetY));
|
||||||
|
// // Update header position to the actual final position
|
||||||
|
// this.headerPosition = { x: Math.round(targetX), y: Math.round(targetY) };
|
||||||
|
// }
|
||||||
|
// this.layoutManager.updateLayout();
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// animate();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// moveToEdge(direction) {
|
||||||
|
// const header = this.windowPool.get('header');
|
||||||
|
// if (!this._isWindowValid(header) || !header.isVisible() || this.isAnimating) return;
|
||||||
|
|
||||||
|
// const display = getCurrentDisplay(header);
|
||||||
|
// const { width, height } = display.workAreaSize;
|
||||||
|
// const { x: workAreaX, y: workAreaY } = display.workArea;
|
||||||
|
// const currentBounds = header.getBounds();
|
||||||
|
|
||||||
|
// const windowSize = {
|
||||||
|
// width: currentBounds.width,
|
||||||
|
// height: currentBounds.height
|
||||||
|
// };
|
||||||
|
|
||||||
|
// let targetX = currentBounds.x;
|
||||||
|
// let targetY = currentBounds.y;
|
||||||
|
|
||||||
|
// switch (direction) {
|
||||||
|
// case 'left':
|
||||||
|
// targetX = workAreaX;
|
||||||
|
// break;
|
||||||
|
// case 'right':
|
||||||
|
// targetX = workAreaX + width - windowSize.width;
|
||||||
|
// break;
|
||||||
|
// case 'up':
|
||||||
|
// targetY = workAreaY;
|
||||||
|
// break;
|
||||||
|
// case 'down':
|
||||||
|
// targetY = workAreaY + height - windowSize.height;
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// header.setBounds({
|
||||||
|
// x: Math.round(targetX),
|
||||||
|
// y: Math.round(targetY),
|
||||||
|
// width: windowSize.width,
|
||||||
|
// height: windowSize.height
|
||||||
|
// });
|
||||||
|
|
||||||
|
// this.headerPosition = { x: targetX, y: targetY };
|
||||||
|
// this.layoutManager.updateLayout();
|
||||||
|
// }
|
||||||
|
|
||||||
|
fade(win, { from, to, duration = 250, onComplete }) {
|
||||||
|
if (!this._isWindowValid(win)) {
|
||||||
|
if (onComplete) onComplete();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const startOpacity = from ?? win.getOpacity();
|
||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
|
|
||||||
if (!Number.isFinite(targetX) || !Number.isFinite(targetY) || !Number.isFinite(startX) || !Number.isFinite(startY)) {
|
const step = () => {
|
||||||
this.isAnimating = false;
|
if (!this._isWindowValid(win)) {
|
||||||
return;
|
if (onComplete) onComplete(); return;
|
||||||
}
|
}
|
||||||
|
const progress = Math.min(1, (Date.now() - startTime) / duration);
|
||||||
const animate = () => {
|
|
||||||
if (!this._isWindowValid(header)) return;
|
|
||||||
|
|
||||||
const elapsed = Date.now() - startTime;
|
|
||||||
const progress = Math.min(elapsed / this.animationDuration, 1);
|
|
||||||
const eased = 1 - Math.pow(1 - progress, 3);
|
const eased = 1 - Math.pow(1 - progress, 3);
|
||||||
const currentX = startX + (targetX - startX) * eased;
|
win.setOpacity(startOpacity + (to - startOpacity) * eased);
|
||||||
const currentY = startY + (targetY - startY) * eased;
|
|
||||||
|
|
||||||
if (!Number.isFinite(currentX) || !Number.isFinite(currentY)) {
|
|
||||||
this.isAnimating = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this._isWindowValid(header)) return;
|
|
||||||
const { width, height } = windowSize || header.getBounds();
|
|
||||||
header.setBounds({
|
|
||||||
x: Math.round(currentX),
|
|
||||||
y: Math.round(currentY),
|
|
||||||
width,
|
|
||||||
height
|
|
||||||
});
|
|
||||||
|
|
||||||
if (progress < 1) {
|
if (progress < 1) {
|
||||||
this.animationFrameId = setTimeout(animate, 8);
|
setTimeout(step, 8);
|
||||||
} else {
|
} else {
|
||||||
this.animationFrameId = null;
|
win.setOpacity(to);
|
||||||
|
if (onComplete) onComplete();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
step();
|
||||||
|
}
|
||||||
|
|
||||||
|
animateWindowBounds(win, targetBounds, options = {}) {
|
||||||
|
if (!this._isWindowValid(win)) {
|
||||||
|
if (options.onComplete) options.onComplete();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.isAnimating = true;
|
||||||
|
const startBounds = win.getBounds();
|
||||||
|
const startTime = Date.now();
|
||||||
|
const duration = options.duration || this.animationDuration;
|
||||||
|
|
||||||
|
const step = () => {
|
||||||
|
if (!this._isWindowValid(win)) {
|
||||||
this.isAnimating = false;
|
this.isAnimating = false;
|
||||||
if (Number.isFinite(targetX) && Number.isFinite(targetY)) {
|
if (options.onComplete) options.onComplete();
|
||||||
if (!this._isWindowValid(header)) return;
|
return;
|
||||||
header.setPosition(Math.round(targetX), Math.round(targetY));
|
|
||||||
// Update header position to the actual final position
|
|
||||||
this.headerPosition = { x: Math.round(targetX), y: Math.round(targetY) };
|
|
||||||
}
|
}
|
||||||
this.layoutManager.updateLayout();
|
|
||||||
|
const progress = Math.min(1, (Date.now() - startTime) / duration);
|
||||||
|
const eased = 1 - Math.pow(1 - progress, 3);
|
||||||
|
|
||||||
|
const newBounds = {
|
||||||
|
x: Math.round(startBounds.x + (targetBounds.x - startBounds.x) * eased),
|
||||||
|
y: Math.round(startBounds.y + (targetBounds.y - startBounds.y) * eased),
|
||||||
|
width: Math.round(startBounds.width + ((targetBounds.width ?? startBounds.width) - startBounds.width) * eased),
|
||||||
|
height: Math.round(startBounds.height + ((targetBounds.height ?? startBounds.height) - startBounds.height) * eased),
|
||||||
|
};
|
||||||
|
win.setBounds(newBounds);
|
||||||
|
|
||||||
|
if (progress < 1) {
|
||||||
|
setTimeout(step, 8);
|
||||||
|
} else {
|
||||||
|
win.setBounds(targetBounds);
|
||||||
|
this.isAnimating = false;
|
||||||
|
if (options.onComplete) options.onComplete();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
animate();
|
step();
|
||||||
}
|
}
|
||||||
|
|
||||||
moveToEdge(direction) {
|
animateWindowPosition(win, targetPosition, options = {}) {
|
||||||
const header = this.windowPool.get('header');
|
if (!this._isWindowValid(win)) {
|
||||||
if (!this._isWindowValid(header) || !header.isVisible() || this.isAnimating) return;
|
if (options.onComplete) options.onComplete();
|
||||||
|
return;
|
||||||
const display = getCurrentDisplay(header);
|
}
|
||||||
const { width, height } = display.workAreaSize;
|
const currentBounds = win.getBounds();
|
||||||
const { x: workAreaX, y: workAreaY } = display.workArea;
|
const targetBounds = { ...currentBounds, ...targetPosition };
|
||||||
const currentBounds = header.getBounds();
|
this.animateWindowBounds(win, targetBounds, options);
|
||||||
|
|
||||||
const windowSize = {
|
|
||||||
width: currentBounds.width,
|
|
||||||
height: currentBounds.height
|
|
||||||
};
|
|
||||||
|
|
||||||
let targetX = currentBounds.x;
|
|
||||||
let targetY = currentBounds.y;
|
|
||||||
|
|
||||||
switch (direction) {
|
|
||||||
case 'left':
|
|
||||||
targetX = workAreaX;
|
|
||||||
break;
|
|
||||||
case 'right':
|
|
||||||
targetX = workAreaX + width - windowSize.width;
|
|
||||||
break;
|
|
||||||
case 'up':
|
|
||||||
targetY = workAreaY;
|
|
||||||
break;
|
|
||||||
case 'down':
|
|
||||||
targetY = workAreaY + height - windowSize.height;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
header.setBounds({
|
animateLayout(layout, animated = true) {
|
||||||
x: Math.round(targetX),
|
if (!layout) return;
|
||||||
y: Math.round(targetY),
|
for (const winName in layout) {
|
||||||
width: windowSize.width,
|
const win = this.windowPool.get(winName);
|
||||||
height: windowSize.height
|
const targetBounds = layout[winName];
|
||||||
});
|
if (win && !win.isDestroyed() && targetBounds) {
|
||||||
|
if (animated) {
|
||||||
this.headerPosition = { x: targetX, y: targetY };
|
this.animateWindowBounds(win, targetBounds);
|
||||||
this.layoutManager.updateLayout();
|
} else {
|
||||||
|
win.setBounds(targetBounds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
|
@ -27,15 +27,15 @@ class WindowLayoutManager {
|
|||||||
this.PADDING = 80;
|
this.PADDING = 80;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateLayout() {
|
// updateLayout() {
|
||||||
if (this.isUpdating) return;
|
// if (this.isUpdating) return;
|
||||||
this.isUpdating = true;
|
// this.isUpdating = true;
|
||||||
|
|
||||||
setImmediate(() => {
|
// setImmediate(() => {
|
||||||
this.positionWindows();
|
// this.positionWindows();
|
||||||
this.isUpdating = false;
|
// this.isUpdating = false;
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
getHeaderPosition = () => {
|
getHeaderPosition = () => {
|
||||||
const header = this.windowPool.get('header');
|
const header = this.windowPool.get('header');
|
||||||
@ -46,215 +46,215 @@ class WindowLayoutManager {
|
|||||||
return { x: 0, y: 0 };
|
return { x: 0, y: 0 };
|
||||||
};
|
};
|
||||||
|
|
||||||
resizeHeaderWindow = ({ width, height }) => {
|
// resizeHeaderWindow = ({ width, height }) => {
|
||||||
const header = this.windowPool.get('header');
|
// const header = this.windowPool.get('header');
|
||||||
if (header) {
|
// if (header) {
|
||||||
console.log(`[WindowManager] Resize request: ${width}x${height}`);
|
// console.log(`[WindowManager] Resize request: ${width}x${height}`);
|
||||||
|
|
||||||
const currentBounds = header.getBounds();
|
// const currentBounds = header.getBounds();
|
||||||
console.log(`[WindowManager] Current bounds: ${currentBounds.width}x${currentBounds.height} at (${currentBounds.x}, ${currentBounds.y})`);
|
// console.log(`[WindowManager] Current bounds: ${currentBounds.width}x${currentBounds.height} at (${currentBounds.x}, ${currentBounds.y})`);
|
||||||
|
|
||||||
if (currentBounds.width === width && currentBounds.height === height) {
|
// if (currentBounds.width === width && currentBounds.height === height) {
|
||||||
console.log('[WindowManager] Already at target size, skipping resize');
|
// console.log('[WindowManager] Already at target size, skipping resize');
|
||||||
return { success: true };
|
// return { success: true };
|
||||||
}
|
// }
|
||||||
|
|
||||||
const wasResizable = header.isResizable();
|
// const wasResizable = header.isResizable();
|
||||||
if (!wasResizable) {
|
// if (!wasResizable) {
|
||||||
header.setResizable(true);
|
// header.setResizable(true);
|
||||||
}
|
// }
|
||||||
|
|
||||||
const centerX = currentBounds.x + currentBounds.width / 2;
|
// const centerX = currentBounds.x + currentBounds.width / 2;
|
||||||
const newX = Math.round(centerX - width / 2);
|
// const newX = Math.round(centerX - width / 2);
|
||||||
|
|
||||||
const display = getCurrentDisplay(header);
|
// const display = getCurrentDisplay(header);
|
||||||
const { x: workAreaX, width: workAreaWidth } = display.workArea;
|
// const { x: workAreaX, width: workAreaWidth } = display.workArea;
|
||||||
|
|
||||||
const clampedX = Math.max(workAreaX, Math.min(workAreaX + workAreaWidth - width, newX));
|
// const clampedX = Math.max(workAreaX, Math.min(workAreaX + workAreaWidth - width, newX));
|
||||||
|
|
||||||
header.setBounds({ x: clampedX, y: currentBounds.y, width, height });
|
// header.setBounds({ x: clampedX, y: currentBounds.y, width, height });
|
||||||
|
|
||||||
if (!wasResizable) {
|
// if (!wasResizable) {
|
||||||
header.setResizable(false);
|
// header.setResizable(false);
|
||||||
}
|
// }
|
||||||
|
|
||||||
this.updateLayout();
|
// this.updateLayout();
|
||||||
|
|
||||||
return { success: true };
|
// return { success: true };
|
||||||
}
|
// }
|
||||||
return { success: false, error: 'Header window not found' };
|
// return { success: false, error: 'Header window not found' };
|
||||||
};
|
// };
|
||||||
|
|
||||||
moveHeaderTo = (newX, newY) => {
|
// moveHeaderTo = (newX, newY) => {
|
||||||
const header = this.windowPool.get('header');
|
// const header = this.windowPool.get('header');
|
||||||
if (header) {
|
// if (header) {
|
||||||
const targetDisplay = screen.getDisplayNearestPoint({ x: newX, y: newY });
|
// const targetDisplay = screen.getDisplayNearestPoint({ x: newX, y: newY });
|
||||||
const { x: workAreaX, y: workAreaY, width, height } = targetDisplay.workArea;
|
// const { x: workAreaX, y: workAreaY, width, height } = targetDisplay.workArea;
|
||||||
const headerBounds = header.getBounds();
|
// const headerBounds = header.getBounds();
|
||||||
|
|
||||||
let clampedX = newX;
|
// let clampedX = newX;
|
||||||
let clampedY = newY;
|
// let clampedY = newY;
|
||||||
|
|
||||||
if (newX < workAreaX) {
|
// if (newX < workAreaX) {
|
||||||
clampedX = workAreaX;
|
// clampedX = workAreaX;
|
||||||
} else if (newX + headerBounds.width > workAreaX + width) {
|
// } else if (newX + headerBounds.width > workAreaX + width) {
|
||||||
clampedX = workAreaX + width - headerBounds.width;
|
// clampedX = workAreaX + width - headerBounds.width;
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (newY < workAreaY) {
|
// if (newY < workAreaY) {
|
||||||
clampedY = workAreaY;
|
// clampedY = workAreaY;
|
||||||
} else if (newY + headerBounds.height > workAreaY + height) {
|
// } else if (newY + headerBounds.height > workAreaY + height) {
|
||||||
clampedY = workAreaY + height - headerBounds.height;
|
// clampedY = workAreaY + height - headerBounds.height;
|
||||||
}
|
// }
|
||||||
|
|
||||||
header.setPosition(clampedX, clampedY, false);
|
// header.setPosition(clampedX, clampedY, false);
|
||||||
this.updateLayout();
|
// this.updateLayout();
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
adjustWindowHeight = (sender, targetHeight) => {
|
// adjustWindowHeight = (sender, targetHeight) => {
|
||||||
const senderWindow = this.windowPool.get(sender);
|
// const senderWindow = this.windowPool.get(sender);
|
||||||
if (senderWindow) {
|
// if (senderWindow) {
|
||||||
const wasResizable = senderWindow.isResizable();
|
// const wasResizable = senderWindow.isResizable();
|
||||||
if (!wasResizable) {
|
// if (!wasResizable) {
|
||||||
senderWindow.setResizable(true);
|
// senderWindow.setResizable(true);
|
||||||
}
|
// }
|
||||||
|
|
||||||
const currentBounds = senderWindow.getBounds();
|
// const currentBounds = senderWindow.getBounds();
|
||||||
const minHeight = senderWindow.getMinimumSize()[1];
|
// const minHeight = senderWindow.getMinimumSize()[1];
|
||||||
const maxHeight = senderWindow.getMaximumSize()[1];
|
// const maxHeight = senderWindow.getMaximumSize()[1];
|
||||||
|
|
||||||
let adjustedHeight;
|
// let adjustedHeight;
|
||||||
if (maxHeight === 0) {
|
// if (maxHeight === 0) {
|
||||||
adjustedHeight = Math.max(minHeight, targetHeight);
|
// adjustedHeight = Math.max(minHeight, targetHeight);
|
||||||
} else {
|
// } else {
|
||||||
adjustedHeight = Math.max(minHeight, Math.min(maxHeight, targetHeight));
|
// adjustedHeight = Math.max(minHeight, Math.min(maxHeight, targetHeight));
|
||||||
}
|
// }
|
||||||
|
|
||||||
senderWindow.setSize(currentBounds.width, adjustedHeight, false);
|
// senderWindow.setSize(currentBounds.width, adjustedHeight, false);
|
||||||
|
|
||||||
if (!wasResizable) {
|
// if (!wasResizable) {
|
||||||
senderWindow.setResizable(false);
|
// senderWindow.setResizable(false);
|
||||||
}
|
// }
|
||||||
|
|
||||||
this.updateLayout();
|
// this.updateLayout();
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
/**
|
// /**
|
||||||
*
|
// *
|
||||||
* @param {object} [visibilityOverride] - { listen: true, ask: true }
|
// * @param {object} [visibilityOverride] - { listen: true, ask: true }
|
||||||
* @returns {{listen: {x:number, y:number}|null, ask: {x:number, y:number}|null}}
|
// * @returns {{listen: {x:number, y:number}|null, ask: {x:number, y:number}|null}}
|
||||||
*/
|
// */
|
||||||
getTargetBoundsForFeatureWindows(visibilityOverride = {}) {
|
// getTargetBoundsForFeatureWindows(visibilityOverride = {}) {
|
||||||
const header = this.windowPool.get('header');
|
// const header = this.windowPool.get('header');
|
||||||
if (!header?.getBounds) return {};
|
// if (!header?.getBounds) return {};
|
||||||
|
|
||||||
const headerBounds = header.getBounds();
|
// const headerBounds = header.getBounds();
|
||||||
const display = getCurrentDisplay(header);
|
// const display = getCurrentDisplay(header);
|
||||||
const { width: screenWidth, height: screenHeight } = display.workAreaSize;
|
// const { width: screenWidth, height: screenHeight } = display.workAreaSize;
|
||||||
const { x: workAreaX, y: workAreaY } = display.workArea;
|
// const { x: workAreaX, y: workAreaY } = display.workArea;
|
||||||
|
|
||||||
const ask = this.windowPool.get('ask');
|
// const ask = this.windowPool.get('ask');
|
||||||
const listen = this.windowPool.get('listen');
|
// const listen = this.windowPool.get('listen');
|
||||||
|
|
||||||
const askVis = visibilityOverride.ask !== undefined ?
|
// const askVis = visibilityOverride.ask !== undefined ?
|
||||||
visibilityOverride.ask :
|
// visibilityOverride.ask :
|
||||||
(ask && ask.isVisible() && !ask.isDestroyed());
|
// (ask && ask.isVisible() && !ask.isDestroyed());
|
||||||
const listenVis = visibilityOverride.listen !== undefined ?
|
// const listenVis = visibilityOverride.listen !== undefined ?
|
||||||
visibilityOverride.listen :
|
// visibilityOverride.listen :
|
||||||
(listen && listen.isVisible() && !listen.isDestroyed());
|
// (listen && listen.isVisible() && !listen.isDestroyed());
|
||||||
|
|
||||||
if (!askVis && !listenVis) return {};
|
// if (!askVis && !listenVis) return {};
|
||||||
|
|
||||||
const PAD = 8;
|
// const PAD = 8;
|
||||||
const headerTopRel = headerBounds.y - workAreaY;
|
// const headerTopRel = headerBounds.y - workAreaY;
|
||||||
const headerBottomRel = headerTopRel + headerBounds.height;
|
// const headerBottomRel = headerTopRel + headerBounds.height;
|
||||||
const headerCenterXRel = headerBounds.x - workAreaX + headerBounds.width / 2;
|
// const headerCenterXRel = headerBounds.x - workAreaX + headerBounds.width / 2;
|
||||||
|
|
||||||
const relativeX = headerCenterXRel / screenWidth;
|
// const relativeX = headerCenterXRel / screenWidth;
|
||||||
const relativeY = (headerBounds.y - workAreaY) / screenHeight;
|
// const relativeY = (headerBounds.y - workAreaY) / screenHeight;
|
||||||
const strategy = this.determineLayoutStrategy(headerBounds, screenWidth, screenHeight, relativeX, relativeY, workAreaX, workAreaY);
|
// const strategy = this.determineLayoutStrategy(headerBounds, screenWidth, screenHeight, relativeX, relativeY, workAreaX, workAreaY);
|
||||||
|
|
||||||
const askB = ask ? ask.getBounds() : null;
|
// const askB = ask ? ask.getBounds() : null;
|
||||||
const listenB = listen ? listen.getBounds() : null;
|
// const listenB = listen ? listen.getBounds() : null;
|
||||||
|
|
||||||
const result = { listen: null, ask: null };
|
// const result = { listen: null, ask: null };
|
||||||
|
|
||||||
if (askVis && listenVis) {
|
// if (askVis && listenVis) {
|
||||||
let askXRel = headerCenterXRel - (askB.width / 2);
|
// let askXRel = headerCenterXRel - (askB.width / 2);
|
||||||
let listenXRel = askXRel - listenB.width - PAD;
|
// let listenXRel = askXRel - listenB.width - PAD;
|
||||||
|
|
||||||
if (listenXRel < PAD) {
|
// if (listenXRel < PAD) {
|
||||||
listenXRel = PAD;
|
// listenXRel = PAD;
|
||||||
askXRel = listenXRel + listenB.width + PAD;
|
// askXRel = listenXRel + listenB.width + PAD;
|
||||||
}
|
// }
|
||||||
if (askXRel + askB.width > screenWidth - PAD) {
|
// if (askXRel + askB.width > screenWidth - PAD) {
|
||||||
askXRel = screenWidth - PAD - askB.width;
|
// askXRel = screenWidth - PAD - askB.width;
|
||||||
listenXRel = askXRel - listenB.width - PAD;
|
// listenXRel = askXRel - listenB.width - PAD;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// [수정] 'above'일 경우 하단 정렬, 'below'일 경우 상단 정렬
|
// // [수정] 'above'일 경우 하단 정렬, 'below'일 경우 상단 정렬
|
||||||
if (strategy.primary === 'above') {
|
// if (strategy.primary === 'above') {
|
||||||
const windowBottomAbs = headerBounds.y - PAD;
|
// const windowBottomAbs = headerBounds.y - PAD;
|
||||||
const askY = windowBottomAbs - askB.height;
|
// const askY = windowBottomAbs - askB.height;
|
||||||
const listenY = windowBottomAbs - listenB.height;
|
// const listenY = windowBottomAbs - listenB.height;
|
||||||
result.ask = { x: Math.round(askXRel + workAreaX), y: Math.round(askY) };
|
// result.ask = { x: Math.round(askXRel + workAreaX), y: Math.round(askY) };
|
||||||
result.listen = { x: Math.round(listenXRel + workAreaX), y: Math.round(listenY) };
|
// result.listen = { x: Math.round(listenXRel + workAreaX), y: Math.round(listenY) };
|
||||||
} else { // 'below'
|
// } else { // 'below'
|
||||||
const yPos = headerBottomRel + PAD;
|
// const yPos = headerBottomRel + PAD;
|
||||||
const yAbs = yPos + workAreaY;
|
// const yAbs = yPos + workAreaY;
|
||||||
result.ask = { x: Math.round(askXRel + workAreaX), y: Math.round(yAbs) };
|
// result.ask = { x: Math.round(askXRel + workAreaX), y: Math.round(yAbs) };
|
||||||
result.listen = { x: Math.round(listenXRel + workAreaX), y: Math.round(yAbs) };
|
// result.listen = { x: Math.round(listenXRel + workAreaX), y: Math.round(yAbs) };
|
||||||
}
|
// }
|
||||||
|
|
||||||
} else { // 한 창만 보일 때는 기존 로직 유지 (정상 동작 확인)
|
// } else { // 한 창만 보일 때는 기존 로직 유지 (정상 동작 확인)
|
||||||
const winB = askVis ? askB : listenB;
|
// const winB = askVis ? askB : listenB;
|
||||||
let xRel = headerCenterXRel - winB.width / 2;
|
// let xRel = headerCenterXRel - winB.width / 2;
|
||||||
xRel = Math.max(PAD, Math.min(screenWidth - winB.width - PAD, xRel));
|
// xRel = Math.max(PAD, Math.min(screenWidth - winB.width - PAD, xRel));
|
||||||
|
|
||||||
let yPos;
|
// let yPos;
|
||||||
if (strategy.primary === 'above') {
|
// if (strategy.primary === 'above') {
|
||||||
const windowBottomRel = headerTopRel - PAD;
|
// const windowBottomRel = headerTopRel - PAD;
|
||||||
yPos = windowBottomRel - winB.height;
|
// yPos = windowBottomRel - winB.height;
|
||||||
} else { // 'below'
|
// } else { // 'below'
|
||||||
yPos = headerBottomRel + PAD;
|
// yPos = headerBottomRel + PAD;
|
||||||
}
|
// }
|
||||||
|
|
||||||
const abs = { x: Math.round(xRel + workAreaX), y: Math.round(yPos + workAreaY) };
|
// const abs = { x: Math.round(xRel + workAreaX), y: Math.round(yPos + workAreaY) };
|
||||||
if (askVis) result.ask = abs;
|
// if (askVis) result.ask = abs;
|
||||||
if (listenVis) result.listen = abs;
|
// if (listenVis) result.listen = abs;
|
||||||
}
|
// }
|
||||||
return result;
|
// return result;
|
||||||
}
|
// }
|
||||||
|
|
||||||
positionWindows() {
|
// positionWindows() {
|
||||||
const header = this.windowPool.get('header');
|
// const header = this.windowPool.get('header');
|
||||||
if (!header?.getBounds) return;
|
// if (!header?.getBounds) return;
|
||||||
|
|
||||||
const headerBounds = header.getBounds();
|
// const headerBounds = header.getBounds();
|
||||||
const display = getCurrentDisplay(header);
|
// const display = getCurrentDisplay(header);
|
||||||
const { width: screenWidth, height: screenHeight } = display.workAreaSize;
|
// const { width: screenWidth, height: screenHeight } = display.workAreaSize;
|
||||||
const { x: workAreaX, y: workAreaY } = display.workArea;
|
// const { x: workAreaX, y: workAreaY } = display.workArea;
|
||||||
|
|
||||||
const headerCenterX = headerBounds.x - workAreaX + headerBounds.width / 2;
|
// const headerCenterX = headerBounds.x - workAreaX + headerBounds.width / 2;
|
||||||
const headerCenterY = headerBounds.y - workAreaY + headerBounds.height / 2;
|
// const headerCenterY = headerBounds.y - workAreaY + headerBounds.height / 2;
|
||||||
|
|
||||||
const relativeX = headerCenterX / screenWidth;
|
// const relativeX = headerCenterX / screenWidth;
|
||||||
const relativeY = headerCenterY / screenHeight;
|
// const relativeY = headerCenterY / screenHeight;
|
||||||
|
|
||||||
const strategy = this.determineLayoutStrategy(headerBounds, screenWidth, screenHeight, relativeX, relativeY, workAreaX, workAreaY);
|
// const strategy = this.determineLayoutStrategy(headerBounds, screenWidth, screenHeight, relativeX, relativeY, workAreaX, workAreaY);
|
||||||
|
|
||||||
this.positionFeatureWindows(headerBounds, strategy, screenWidth, screenHeight, workAreaX, workAreaY);
|
// this.positionFeatureWindows(headerBounds, strategy, screenWidth, screenHeight, workAreaX, workAreaY);
|
||||||
const settings = this.windowPool.get('settings');
|
// const settings = this.windowPool.get('settings');
|
||||||
if (settings && !settings.isDestroyed() && settings.isVisible()) {
|
// if (settings && !settings.isDestroyed() && settings.isVisible()) {
|
||||||
const settingPos = this.calculateSettingsWindowPosition();
|
// const settingPos = this.calculateSettingsWindowPosition();
|
||||||
if (settingPos) {
|
// if (settingPos) {
|
||||||
const { width, height } = settings.getBounds();
|
// const { width, height } = settings.getBounds();
|
||||||
settings.setBounds({ x: settingPos.x, y: settingPos.y, width, height });
|
// settings.setBounds({ x: settingPos.x, y: settingPos.y, width, height });
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -283,67 +283,67 @@ class WindowLayoutManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
positionFeatureWindows(headerBounds, strategy, screenWidth, screenHeight, workAreaX, workAreaY) {
|
// positionFeatureWindows(headerBounds, strategy, screenWidth, screenHeight, workAreaX, workAreaY) {
|
||||||
const ask = this.windowPool.get('ask');
|
// const ask = this.windowPool.get('ask');
|
||||||
const listen = this.windowPool.get('listen');
|
// const listen = this.windowPool.get('listen');
|
||||||
const askVisible = ask && ask.isVisible() && !ask.isDestroyed();
|
// const askVisible = ask && ask.isVisible() && !ask.isDestroyed();
|
||||||
const listenVisible = listen && listen.isVisible() && !listen.isDestroyed();
|
// const listenVisible = listen && listen.isVisible() && !listen.isDestroyed();
|
||||||
|
|
||||||
if (!askVisible && !listenVisible) return;
|
// if (!askVisible && !listenVisible) return;
|
||||||
|
|
||||||
const PAD = 8;
|
// const PAD = 8;
|
||||||
const headerTopRel = headerBounds.y - workAreaY;
|
// const headerTopRel = headerBounds.y - workAreaY;
|
||||||
const headerBottomRel = headerTopRel + headerBounds.height;
|
// const headerBottomRel = headerTopRel + headerBounds.height;
|
||||||
const headerCenterXRel = headerBounds.x - workAreaX + headerBounds.width / 2;
|
// const headerCenterXRel = headerBounds.x - workAreaX + headerBounds.width / 2;
|
||||||
|
|
||||||
let askBounds = askVisible ? ask.getBounds() : null;
|
// let askBounds = askVisible ? ask.getBounds() : null;
|
||||||
let listenBounds = listenVisible ? listen.getBounds() : null;
|
// let listenBounds = listenVisible ? listen.getBounds() : null;
|
||||||
|
|
||||||
if (askVisible && listenVisible) {
|
// if (askVisible && listenVisible) {
|
||||||
let askXRel = headerCenterXRel - (askBounds.width / 2);
|
// let askXRel = headerCenterXRel - (askBounds.width / 2);
|
||||||
let listenXRel = askXRel - listenBounds.width - PAD;
|
// let listenXRel = askXRel - listenBounds.width - PAD;
|
||||||
|
|
||||||
if (listenXRel < PAD) {
|
// if (listenXRel < PAD) {
|
||||||
listenXRel = PAD;
|
// listenXRel = PAD;
|
||||||
askXRel = listenXRel + listenBounds.width + PAD;
|
// askXRel = listenXRel + listenBounds.width + PAD;
|
||||||
}
|
// }
|
||||||
if (askXRel + askBounds.width > screenWidth - PAD) {
|
// if (askXRel + askBounds.width > screenWidth - PAD) {
|
||||||
askXRel = screenWidth - PAD - askBounds.width;
|
// askXRel = screenWidth - PAD - askBounds.width;
|
||||||
listenXRel = askXRel - listenBounds.width - PAD;
|
// listenXRel = askXRel - listenBounds.width - PAD;
|
||||||
}
|
// }
|
||||||
|
|
||||||
// [수정] 'above'일 경우 하단 정렬, 'below'일 경우 상단 정렬
|
// // [수정] 'above'일 경우 하단 정렬, 'below'일 경우 상단 정렬
|
||||||
if (strategy.primary === 'above') {
|
// if (strategy.primary === 'above') {
|
||||||
const windowBottomAbs = headerBounds.y - PAD;
|
// const windowBottomAbs = headerBounds.y - PAD;
|
||||||
const askY = windowBottomAbs - askBounds.height;
|
// const askY = windowBottomAbs - askBounds.height;
|
||||||
const listenY = windowBottomAbs - listenBounds.height;
|
// const listenY = windowBottomAbs - listenBounds.height;
|
||||||
ask.setBounds({ x: Math.round(askXRel + workAreaX), y: Math.round(askY), width: askBounds.width, height: askBounds.height });
|
// ask.setBounds({ x: Math.round(askXRel + workAreaX), y: Math.round(askY), width: askBounds.width, height: askBounds.height });
|
||||||
listen.setBounds({ x: Math.round(listenXRel + workAreaX), y: Math.round(listenY), width: listenBounds.width, height: listenBounds.height });
|
// listen.setBounds({ x: Math.round(listenXRel + workAreaX), y: Math.round(listenY), width: listenBounds.width, height: listenBounds.height });
|
||||||
} else { // 'below'
|
// } else { // 'below'
|
||||||
const yPos = headerBottomRel + PAD;
|
// const yPos = headerBottomRel + PAD;
|
||||||
const yAbs = yPos + workAreaY;
|
// const yAbs = yPos + workAreaY;
|
||||||
ask.setBounds({ x: Math.round(askXRel + workAreaX), y: Math.round(yAbs), width: askBounds.width, height: askBounds.height });
|
// ask.setBounds({ x: Math.round(askXRel + workAreaX), y: Math.round(yAbs), width: askBounds.width, height: askBounds.height });
|
||||||
listen.setBounds({ x: Math.round(listenXRel + workAreaX), y: Math.round(yAbs), width: listenBounds.width, height: listenBounds.height });
|
// listen.setBounds({ x: Math.round(listenXRel + workAreaX), y: Math.round(yAbs), width: listenBounds.width, height: listenBounds.height });
|
||||||
}
|
// }
|
||||||
|
|
||||||
} else { // 한 창만 보일 때는 기존 로직 유지 (정상 동작 확인)
|
// } else { // 한 창만 보일 때는 기존 로직 유지 (정상 동작 확인)
|
||||||
const win = askVisible ? ask : listen;
|
// const win = askVisible ? ask : listen;
|
||||||
const winBounds = askVisible ? askBounds : listenBounds;
|
// const winBounds = askVisible ? askBounds : listenBounds;
|
||||||
let xRel = headerCenterXRel - winBounds.width / 2;
|
// let xRel = headerCenterXRel - winBounds.width / 2;
|
||||||
xRel = Math.max(PAD, Math.min(screenWidth - winBounds.width - PAD, xRel));
|
// xRel = Math.max(PAD, Math.min(screenWidth - winBounds.width - PAD, xRel));
|
||||||
|
|
||||||
let yPos;
|
// let yPos;
|
||||||
if (strategy.primary === 'above') {
|
// if (strategy.primary === 'above') {
|
||||||
const windowBottomRel = headerTopRel - PAD;
|
// const windowBottomRel = headerTopRel - PAD;
|
||||||
yPos = windowBottomRel - winBounds.height;
|
// yPos = windowBottomRel - winBounds.height;
|
||||||
} else { // 'below'
|
// } else { // 'below'
|
||||||
yPos = headerBottomRel + PAD;
|
// yPos = headerBottomRel + PAD;
|
||||||
}
|
// }
|
||||||
const yAbs = yPos + workAreaY;
|
// const yAbs = yPos + workAreaY;
|
||||||
|
|
||||||
win.setBounds({ x: Math.round(xRel + workAreaX), y: Math.round(yAbs), width: winBounds.width, height: winBounds.height });
|
// win.setBounds({ x: Math.round(xRel + workAreaX), y: Math.round(yAbs), width: winBounds.width, height: winBounds.height });
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {{x: number, y: number} | null}
|
* @returns {{x: number, y: number} | null}
|
||||||
@ -373,18 +373,142 @@ class WindowLayoutManager {
|
|||||||
return { x: Math.round(clampedX), y: Math.round(clampedY) };
|
return { x: Math.round(clampedX), y: Math.round(clampedY) };
|
||||||
}
|
}
|
||||||
|
|
||||||
positionShortcutSettingsWindow() {
|
// positionShortcutSettingsWindow() {
|
||||||
|
// const header = this.windowPool.get('header');
|
||||||
|
// const shortcutSettings = this.windowPool.get('shortcut-settings');
|
||||||
|
|
||||||
|
// if (!header || header.isDestroyed() || !shortcutSettings || shortcutSettings.isDestroyed()) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const headerBounds = header.getBounds();
|
||||||
|
// const shortcutBounds = shortcutSettings.getBounds();
|
||||||
|
// const display = getCurrentDisplay(header);
|
||||||
|
// const { workArea } = display;
|
||||||
|
|
||||||
|
// let newX = Math.round(headerBounds.x + (headerBounds.width / 2) - (shortcutBounds.width / 2));
|
||||||
|
// let newY = Math.round(headerBounds.y);
|
||||||
|
|
||||||
|
// newX = Math.max(workArea.x, Math.min(newX, workArea.x + workArea.width - shortcutBounds.width));
|
||||||
|
// newY = Math.max(workArea.y, Math.min(newY, workArea.y + workArea.height - shortcutBounds.height));
|
||||||
|
|
||||||
|
// shortcutSettings.setBounds({ x: newX, y: newY, width: shortcutBounds.width, height: shortcutBounds.height });
|
||||||
|
// }
|
||||||
|
|
||||||
|
calculateHeaderResize(header, { width, height }) {
|
||||||
|
if (!header) return null;
|
||||||
|
const currentBounds = header.getBounds();
|
||||||
|
const centerX = currentBounds.x + currentBounds.width / 2;
|
||||||
|
const newX = Math.round(centerX - width / 2);
|
||||||
|
const display = getCurrentDisplay(header);
|
||||||
|
const { x: workAreaX, width: workAreaWidth } = display.workArea;
|
||||||
|
const clampedX = Math.max(workAreaX, Math.min(workAreaX + workAreaWidth - width, newX));
|
||||||
|
return { x: clampedX, y: currentBounds.y, width, height };
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateClampedPosition(header, { x: newX, y: newY }) {
|
||||||
|
if (!header) return null;
|
||||||
|
const targetDisplay = screen.getDisplayNearestPoint({ x: newX, y: newY });
|
||||||
|
const { x: workAreaX, y: workAreaY, width, height } = targetDisplay.workArea;
|
||||||
|
const headerBounds = header.getBounds();
|
||||||
|
const clampedX = Math.max(workAreaX, Math.min(newX, workAreaX + width - headerBounds.width));
|
||||||
|
const clampedY = Math.max(workAreaY, Math.min(newY, workAreaY + height - headerBounds.height));
|
||||||
|
return { x: clampedX, y: clampedY };
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateWindowHeightAdjustment(senderWindow, targetHeight) {
|
||||||
|
if (!senderWindow) return null;
|
||||||
|
const currentBounds = senderWindow.getBounds();
|
||||||
|
const minHeight = senderWindow.getMinimumSize()[1];
|
||||||
|
const maxHeight = senderWindow.getMaximumSize()[1];
|
||||||
|
let adjustedHeight = Math.max(minHeight, targetHeight);
|
||||||
|
if (maxHeight > 0) {
|
||||||
|
adjustedHeight = Math.min(maxHeight, adjustedHeight);
|
||||||
|
}
|
||||||
|
return { ...currentBounds, height: adjustedHeight };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 기존 getTargetBoundsForFeatureWindows를 이 함수로 대체합니다.
|
||||||
|
calculateFeatureWindowLayout(visibility) {
|
||||||
|
const header = this.windowPool.get('header');
|
||||||
|
if (!header?.getBounds) return {};
|
||||||
|
|
||||||
|
const headerBounds = header.getBounds();
|
||||||
|
const display = getCurrentDisplay(header);
|
||||||
|
const { width: screenWidth, height: screenHeight, x: workAreaX, y: workAreaY } = display.workArea;
|
||||||
|
|
||||||
|
const ask = this.windowPool.get('ask');
|
||||||
|
const listen = this.windowPool.get('listen');
|
||||||
|
|
||||||
|
const askVis = visibility.ask && ask && !ask.isDestroyed();
|
||||||
|
const listenVis = visibility.listen && listen && !listen.isDestroyed();
|
||||||
|
|
||||||
|
if (!askVis && !listenVis) return {};
|
||||||
|
|
||||||
|
const PAD = 8;
|
||||||
|
const headerTopRel = headerBounds.y - workAreaY;
|
||||||
|
const headerBottomRel = headerTopRel + headerBounds.height;
|
||||||
|
const headerCenterXRel = headerBounds.x - workAreaX + headerBounds.width / 2;
|
||||||
|
|
||||||
|
const relativeX = headerCenterXRel / screenWidth;
|
||||||
|
const relativeY = (headerBounds.y - workAreaY) / screenHeight;
|
||||||
|
const strategy = this.determineLayoutStrategy(headerBounds, screenWidth, screenHeight, relativeX, relativeY, workAreaX, workAreaY);
|
||||||
|
|
||||||
|
const askB = askVis ? ask.getBounds() : null;
|
||||||
|
const listenB = listenVis ? listen.getBounds() : null;
|
||||||
|
|
||||||
|
const layout = {};
|
||||||
|
|
||||||
|
if (askVis && listenVis) {
|
||||||
|
let askXRel = headerCenterXRel - (askB.width / 2);
|
||||||
|
let listenXRel = askXRel - listenB.width - PAD;
|
||||||
|
|
||||||
|
if (listenXRel < PAD) {
|
||||||
|
listenXRel = PAD;
|
||||||
|
askXRel = listenXRel + listenB.width + PAD;
|
||||||
|
}
|
||||||
|
if (askXRel + askB.width > screenWidth - PAD) {
|
||||||
|
askXRel = screenWidth - PAD - askB.width;
|
||||||
|
listenXRel = askXRel - listenB.width - PAD;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strategy.primary === 'above') {
|
||||||
|
const windowBottomAbs = headerBounds.y - PAD;
|
||||||
|
layout.ask = { x: Math.round(askXRel + workAreaX), y: Math.round(windowBottomAbs - askB.height), width: askB.width, height: askB.height };
|
||||||
|
layout.listen = { x: Math.round(listenXRel + workAreaX), y: Math.round(windowBottomAbs - listenB.height), width: listenB.width, height: listenB.height };
|
||||||
|
} else { // 'below'
|
||||||
|
const yAbs = headerBounds.y + headerBounds.height + PAD;
|
||||||
|
layout.ask = { x: Math.round(askXRel + workAreaX), y: Math.round(yAbs), width: askB.width, height: askB.height };
|
||||||
|
layout.listen = { x: Math.round(listenXRel + workAreaX), y: Math.round(yAbs), width: listenB.width, height: listenB.height };
|
||||||
|
}
|
||||||
|
} else { // Single window
|
||||||
|
const winName = askVis ? 'ask' : 'listen';
|
||||||
|
const winB = askVis ? askB : listenB;
|
||||||
|
if (!winB) return {};
|
||||||
|
|
||||||
|
let xRel = headerCenterXRel - winB.width / 2;
|
||||||
|
xRel = Math.max(PAD, Math.min(screenWidth - winB.width - PAD, xRel));
|
||||||
|
|
||||||
|
let yPos;
|
||||||
|
if (strategy.primary === 'above') {
|
||||||
|
yPos = (headerBounds.y - workAreaY) - PAD - winB.height;
|
||||||
|
} else { // 'below'
|
||||||
|
yPos = (headerBounds.y - workAreaY) + headerBounds.height + PAD;
|
||||||
|
}
|
||||||
|
|
||||||
|
layout[winName] = { x: Math.round(xRel + workAreaX), y: Math.round(yPos + workAreaY), width: winB.width, height: winB.height };
|
||||||
|
}
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateShortcutSettingsWindowPosition() {
|
||||||
const header = this.windowPool.get('header');
|
const header = this.windowPool.get('header');
|
||||||
const shortcutSettings = this.windowPool.get('shortcut-settings');
|
const shortcutSettings = this.windowPool.get('shortcut-settings');
|
||||||
|
if (!header || !shortcutSettings) return null;
|
||||||
if (!header || header.isDestroyed() || !shortcutSettings || shortcutSettings.isDestroyed()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const headerBounds = header.getBounds();
|
const headerBounds = header.getBounds();
|
||||||
const shortcutBounds = shortcutSettings.getBounds();
|
const shortcutBounds = shortcutSettings.getBounds();
|
||||||
const display = getCurrentDisplay(header);
|
const { workArea } = getCurrentDisplay(header);
|
||||||
const { workArea } = display;
|
|
||||||
|
|
||||||
let newX = Math.round(headerBounds.x + (headerBounds.width / 2) - (shortcutBounds.width / 2));
|
let newX = Math.round(headerBounds.x + (headerBounds.width / 2) - (shortcutBounds.width / 2));
|
||||||
let newY = Math.round(headerBounds.y);
|
let newY = Math.round(headerBounds.y);
|
||||||
@ -392,7 +516,65 @@ class WindowLayoutManager {
|
|||||||
newX = Math.max(workArea.x, Math.min(newX, workArea.x + workArea.width - shortcutBounds.width));
|
newX = Math.max(workArea.x, Math.min(newX, workArea.x + workArea.width - shortcutBounds.width));
|
||||||
newY = Math.max(workArea.y, Math.min(newY, workArea.y + workArea.height - shortcutBounds.height));
|
newY = Math.max(workArea.y, Math.min(newY, workArea.y + workArea.height - shortcutBounds.height));
|
||||||
|
|
||||||
shortcutSettings.setBounds({ x: newX, y: newY, width: shortcutBounds.width, height: shortcutBounds.height });
|
return { x: newX, y: newY, width: shortcutBounds.width, height: shortcutBounds.height };
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateStepMovePosition(header, direction) {
|
||||||
|
if (!header) return null;
|
||||||
|
const currentBounds = header.getBounds();
|
||||||
|
const stepSize = 80; // 이동 간격
|
||||||
|
let targetX = currentBounds.x;
|
||||||
|
let targetY = currentBounds.y;
|
||||||
|
|
||||||
|
switch (direction) {
|
||||||
|
case 'left': targetX -= stepSize; break;
|
||||||
|
case 'right': targetX += stepSize; break;
|
||||||
|
case 'up': targetY -= stepSize; break;
|
||||||
|
case 'down': targetY += stepSize; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.calculateClampedPosition(header, { x: targetX, y: targetY });
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateEdgePosition(header, direction) {
|
||||||
|
if (!header) return null;
|
||||||
|
const display = getCurrentDisplay(header);
|
||||||
|
const { workArea } = display;
|
||||||
|
const currentBounds = header.getBounds();
|
||||||
|
|
||||||
|
let targetX = currentBounds.x;
|
||||||
|
let targetY = currentBounds.y;
|
||||||
|
|
||||||
|
switch (direction) {
|
||||||
|
case 'left': targetX = workArea.x; break;
|
||||||
|
case 'right': targetX = workArea.x + workArea.width - currentBounds.width; break;
|
||||||
|
case 'up': targetY = workArea.y; break;
|
||||||
|
case 'down': targetY = workArea.y + workArea.height - currentBounds.height; break;
|
||||||
|
}
|
||||||
|
return { x: targetX, y: targetY };
|
||||||
|
}
|
||||||
|
|
||||||
|
calculateNewPositionForDisplay(window, targetDisplayId) {
|
||||||
|
if (!window) return null;
|
||||||
|
|
||||||
|
const targetDisplay = screen.getAllDisplays().find(d => d.id === targetDisplayId);
|
||||||
|
if (!targetDisplay) return null;
|
||||||
|
|
||||||
|
const currentBounds = window.getBounds();
|
||||||
|
const currentDisplay = getCurrentDisplay(window);
|
||||||
|
|
||||||
|
if (currentDisplay.id === targetDisplay.id) return { x: currentBounds.x, y: currentBounds.y };
|
||||||
|
|
||||||
|
const relativeX = (currentBounds.x - currentDisplay.workArea.x) / currentDisplay.workArea.width;
|
||||||
|
const relativeY = (currentBounds.y - currentDisplay.workArea.y) / currentDisplay.workArea.height;
|
||||||
|
|
||||||
|
const targetX = targetDisplay.workArea.x + targetDisplay.workArea.width * relativeX;
|
||||||
|
const targetY = targetDisplay.workArea.y + targetDisplay.workArea.height * relativeY;
|
||||||
|
|
||||||
|
const clampedX = Math.max(targetDisplay.workArea.x, Math.min(targetX, targetDisplay.workArea.x + targetDisplay.workArea.width - currentBounds.width));
|
||||||
|
const clampedY = Math.max(targetDisplay.workArea.y, Math.min(targetY, targetDisplay.workArea.y + targetDisplay.workArea.height - currentBounds.height));
|
||||||
|
|
||||||
|
return { x: Math.round(clampedX), y: Math.round(clampedY) };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -40,41 +40,61 @@ let settingsHideTimer = null;
|
|||||||
let layoutManager = null;
|
let layoutManager = null;
|
||||||
let movementManager = null;
|
let movementManager = null;
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {BrowserWindow} win
|
|
||||||
* @param {number} from
|
|
||||||
* @param {number} to
|
|
||||||
* @param {number} duration
|
|
||||||
* @param {Function=} onComplete
|
|
||||||
*/
|
|
||||||
function fadeWindow(win, from, to, duration = 250, onComplete) {
|
|
||||||
if (!win || win.isDestroyed()) return;
|
|
||||||
|
|
||||||
const FPS = 60;
|
function updateChildWindowLayouts(animated = true) {
|
||||||
const steps = Math.max(1, Math.round(duration / (1000 / FPS)));
|
if (movementManager.isAnimating) return;
|
||||||
let currentStep = 0;
|
|
||||||
|
|
||||||
win.setOpacity(from);
|
const visibleWindows = {};
|
||||||
|
const listenWin = windowPool.get('listen');
|
||||||
const timer = setInterval(() => {
|
const askWin = windowPool.get('ask');
|
||||||
if (win.isDestroyed()) { clearInterval(timer); return; }
|
if (listenWin && !listenWin.isDestroyed() && listenWin.isVisible()) {
|
||||||
|
visibleWindows.listen = true;
|
||||||
currentStep += 1;
|
|
||||||
const progress = currentStep / steps;
|
|
||||||
const eased = progress < 1
|
|
||||||
? 1 - Math.pow(1 - progress, 3)
|
|
||||||
: 1;
|
|
||||||
|
|
||||||
win.setOpacity(from + (to - from) * eased);
|
|
||||||
|
|
||||||
if (currentStep >= steps) {
|
|
||||||
clearInterval(timer);
|
|
||||||
win.setOpacity(to);
|
|
||||||
onComplete && onComplete();
|
|
||||||
}
|
}
|
||||||
}, 1000 / FPS);
|
if (askWin && !askWin.isDestroyed() && askWin.isVisible()) {
|
||||||
|
visibleWindows.ask = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Object.keys(visibleWindows).length === 0) return;
|
||||||
|
|
||||||
|
const newLayout = layoutManager.calculateFeatureWindowLayout(visibleWindows);
|
||||||
|
movementManager.animateLayout(newLayout, animated);
|
||||||
|
}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * @param {BrowserWindow} win
|
||||||
|
// * @param {number} from
|
||||||
|
// * @param {number} to
|
||||||
|
// * @param {number} duration
|
||||||
|
// * @param {Function=} onComplete
|
||||||
|
// */
|
||||||
|
// function fadeWindow(win, from, to, duration = 250, onComplete) {
|
||||||
|
// if (!win || win.isDestroyed()) return;
|
||||||
|
|
||||||
|
// const FPS = 60;
|
||||||
|
// const steps = Math.max(1, Math.round(duration / (1000 / FPS)));
|
||||||
|
// let currentStep = 0;
|
||||||
|
|
||||||
|
// win.setOpacity(from);
|
||||||
|
|
||||||
|
// const timer = setInterval(() => {
|
||||||
|
// if (win.isDestroyed()) { clearInterval(timer); return; }
|
||||||
|
|
||||||
|
// currentStep += 1;
|
||||||
|
// const progress = currentStep / steps;
|
||||||
|
// const eased = progress < 1
|
||||||
|
// ? 1 - Math.pow(1 - progress, 3)
|
||||||
|
// : 1;
|
||||||
|
|
||||||
|
// win.setOpacity(from + (to - from) * eased);
|
||||||
|
|
||||||
|
// if (currentStep >= steps) {
|
||||||
|
// clearInterval(timer);
|
||||||
|
// win.setOpacity(to);
|
||||||
|
// onComplete && onComplete();
|
||||||
|
// }
|
||||||
|
// }, 1000 / FPS);
|
||||||
|
// }
|
||||||
|
|
||||||
const showSettingsWindow = () => {
|
const showSettingsWindow = () => {
|
||||||
internalBridge.emit('window:requestVisibility', { name: 'settings', visible: true });
|
internalBridge.emit('window:requestVisibility', { name: 'settings', visible: true });
|
||||||
};
|
};
|
||||||
@ -112,6 +132,39 @@ const adjustWindowHeight = (sender, targetHeight) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// function setupWindowController(windowPool, layoutManager, movementManager) {
|
||||||
|
// internalBridge.on('window:requestVisibility', ({ name, visible }) => {
|
||||||
|
// handleWindowVisibilityRequest(windowPool, layoutManager, movementManager, name, visible);
|
||||||
|
// });
|
||||||
|
// internalBridge.on('window:requestToggleAllWindowsVisibility', ({ targetVisibility }) => {
|
||||||
|
// changeAllWindowsVisibility(windowPool, targetVisibility);
|
||||||
|
// });
|
||||||
|
// internalBridge.on('window:moveToDisplay', ({ displayId }) => {
|
||||||
|
// movementManager.moveToDisplay(displayId);
|
||||||
|
// });
|
||||||
|
// internalBridge.on('window:moveToEdge', ({ direction }) => {
|
||||||
|
// movementManager.moveToEdge(direction);
|
||||||
|
// });
|
||||||
|
// internalBridge.on('window:moveStep', ({ direction }) => {
|
||||||
|
// movementManager.moveStep(direction);
|
||||||
|
// });
|
||||||
|
// internalBridge.on('window:resizeHeaderWindow', ({ width, height }) => {
|
||||||
|
// resizingHeaderWindow(layoutManager, movementManager, { width, height });
|
||||||
|
// });
|
||||||
|
// internalBridge.on('window:headerAnimationFinished', (state) => {
|
||||||
|
// handlingHeaderAnimationFinished(windowPool, layoutManager, state);
|
||||||
|
// });
|
||||||
|
// internalBridge.on('window:getHeaderPosition', () => {
|
||||||
|
// gettingHeaderPosition(layoutManager);
|
||||||
|
// });
|
||||||
|
// internalBridge.on('window:moveHeaderTo', ({ newX, newY }) => {
|
||||||
|
// movingHeaderTo(layoutManager, newX, newY);
|
||||||
|
// });
|
||||||
|
// internalBridge.on('window:adjustWindowHeight', ({ sender, targetHeight }) => {
|
||||||
|
// adjustingWindowHeight(layoutManager, sender, targetHeight);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
function setupWindowController(windowPool, layoutManager, movementManager) {
|
function setupWindowController(windowPool, layoutManager, movementManager) {
|
||||||
internalBridge.on('window:requestVisibility', ({ name, visible }) => {
|
internalBridge.on('window:requestVisibility', ({ name, visible }) => {
|
||||||
handleWindowVisibilityRequest(windowPool, layoutManager, movementManager, name, visible);
|
handleWindowVisibilityRequest(windowPool, layoutManager, movementManager, name, visible);
|
||||||
@ -120,28 +173,90 @@ function setupWindowController(windowPool, layoutManager, movementManager) {
|
|||||||
changeAllWindowsVisibility(windowPool, targetVisibility);
|
changeAllWindowsVisibility(windowPool, targetVisibility);
|
||||||
});
|
});
|
||||||
internalBridge.on('window:moveToDisplay', ({ displayId }) => {
|
internalBridge.on('window:moveToDisplay', ({ displayId }) => {
|
||||||
movementManager.moveToDisplay(displayId);
|
// movementManager.moveToDisplay(displayId);
|
||||||
|
const header = windowPool.get('header');
|
||||||
|
if (header) {
|
||||||
|
const newPosition = layoutManager.calculateNewPositionForDisplay(header, displayId);
|
||||||
|
if (newPosition) {
|
||||||
|
movementManager.animateWindowPosition(header, newPosition, {
|
||||||
|
onComplete: () => updateChildWindowLayouts(true)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
internalBridge.on('window:moveToEdge', ({ direction }) => {
|
internalBridge.on('window:moveToEdge', ({ direction }) => {
|
||||||
movementManager.moveToEdge(direction);
|
const header = windowPool.get('header');
|
||||||
|
if (header) {
|
||||||
|
const newPosition = layoutManager.calculateEdgePosition(header, direction);
|
||||||
|
movementManager.animateWindowPosition(header, newPosition, {
|
||||||
|
onComplete: () => updateChildWindowLayouts(true)
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
internalBridge.on('window:moveStep', ({ direction }) => {
|
internalBridge.on('window:moveStep', ({ direction }) => {
|
||||||
movementManager.moveStep(direction);
|
const header = windowPool.get('header');
|
||||||
|
if (header) {
|
||||||
|
const newPosition = layoutManager.calculateStepMovePosition(header, direction);
|
||||||
|
movementManager.animateWindowPosition(header, newPosition, {
|
||||||
|
onComplete: () => updateChildWindowLayouts(true)
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
internalBridge.on('window:resizeHeaderWindow', ({ width, height }) => {
|
internalBridge.on('window:resizeHeaderWindow', ({ width, height }) => {
|
||||||
resizingHeaderWindow(layoutManager, movementManager, { width, height });
|
const header = windowPool.get('header');
|
||||||
|
if (!header || movementManager.isAnimating) return;
|
||||||
|
|
||||||
|
const newHeaderBounds = layoutManager.calculateHeaderResize(header, { width, height });
|
||||||
|
|
||||||
|
const wasResizable = header.isResizable();
|
||||||
|
if (!wasResizable) header.setResizable(true);
|
||||||
|
|
||||||
|
movementManager.animateWindowBounds(header, newHeaderBounds, {
|
||||||
|
onComplete: () => {
|
||||||
|
if (!wasResizable) header.setResizable(false);
|
||||||
|
updateChildWindowLayouts(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
internalBridge.on('window:headerAnimationFinished', (state) => {
|
internalBridge.on('window:headerAnimationFinished', (state) => {
|
||||||
handlingHeaderAnimationFinished(windowPool, layoutManager, state);
|
const header = windowPool.get('header');
|
||||||
|
if (!header || header.isDestroyed()) return;
|
||||||
|
|
||||||
|
if (state === 'hidden') {
|
||||||
|
header.hide();
|
||||||
|
} else if (state === 'visible') {
|
||||||
|
updateChildWindowLayouts(false);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
internalBridge.on('window:getHeaderPosition', () => {
|
internalBridge.on('window:getHeaderPosition', () => {
|
||||||
gettingHeaderPosition(layoutManager);
|
const header = windowPool.get('header');
|
||||||
|
if (header) return header.getBounds();
|
||||||
|
return { x: 0, y: 0 };
|
||||||
});
|
});
|
||||||
internalBridge.on('window:moveHeaderTo', ({ newX, newY }) => {
|
internalBridge.on('window:moveHeaderTo', ({ newX, newY }) => {
|
||||||
movingHeaderTo(layoutManager, newX, newY);
|
const header = windowPool.get('header');
|
||||||
|
if (header) {
|
||||||
|
const newPosition = layoutManager.calculateClampedPosition(header, { x: newX, y: newY });
|
||||||
|
movementManager.animateWindowPosition(header, newPosition, {
|
||||||
|
onComplete: () => updateChildWindowLayouts(true)
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
internalBridge.on('window:adjustWindowHeight', ({ sender, targetHeight }) => {
|
internalBridge.on('window:adjustWindowHeight', ({ sender, targetHeight }) => {
|
||||||
adjustingWindowHeight(layoutManager, sender, targetHeight);
|
const senderWindow = windowPool.get(sender);
|
||||||
|
if (senderWindow) {
|
||||||
|
const newBounds = layoutManager.calculateWindowHeightAdjustment(senderWindow, targetHeight);
|
||||||
|
|
||||||
|
const wasResizable = senderWindow.isResizable();
|
||||||
|
if (!wasResizable) senderWindow.setResizable(true);
|
||||||
|
|
||||||
|
movementManager.animateWindowBounds(senderWindow, newBounds, {
|
||||||
|
onComplete: () => {
|
||||||
|
if (!wasResizable) senderWindow.setResizable(false);
|
||||||
|
updateChildWindowLayouts(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -257,7 +372,10 @@ async function handleWindowVisibilityRequest(windowPool, layoutManager, movement
|
|||||||
|
|
||||||
if (name === 'shortcut-settings') {
|
if (name === 'shortcut-settings') {
|
||||||
if (shouldBeVisible) {
|
if (shouldBeVisible) {
|
||||||
layoutManager.positionShortcutSettingsWindow();
|
// layoutManager.positionShortcutSettingsWindow();
|
||||||
|
const newBounds = layoutManager.calculateShortcutSettingsWindowPosition();
|
||||||
|
if (newBounds) win.setBounds(newBounds);
|
||||||
|
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
win.setAlwaysOnTop(true, 'screen-saver');
|
win.setAlwaysOnTop(true, 'screen-saver');
|
||||||
} else {
|
} else {
|
||||||
@ -278,7 +396,96 @@ async function handleWindowVisibilityRequest(windowPool, layoutManager, movement
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if (name === 'listen' || name === 'ask') {
|
||||||
|
// const otherName = name === 'listen' ? 'ask' : 'listen';
|
||||||
|
// const otherWin = windowPool.get(otherName);
|
||||||
|
// const isOtherWinVisible = otherWin && !otherWin.isDestroyed() && otherWin.isVisible();
|
||||||
|
|
||||||
|
// const ANIM_OFFSET_X = 50;
|
||||||
|
// const ANIM_OFFSET_Y = 20;
|
||||||
|
|
||||||
|
// if (shouldBeVisible) {
|
||||||
|
// win.setOpacity(0);
|
||||||
|
|
||||||
|
// if (name === 'listen') {
|
||||||
|
// if (!isOtherWinVisible) {
|
||||||
|
// const targets = layoutManager.getTargetBoundsForFeatureWindows({ listen: true, ask: false });
|
||||||
|
// if (!targets.listen) return;
|
||||||
|
|
||||||
|
// const startPos = { x: targets.listen.x - ANIM_OFFSET_X, y: targets.listen.y };
|
||||||
|
// win.setBounds(startPos);
|
||||||
|
// win.show();
|
||||||
|
// fadeWindow(win, 0, 1);
|
||||||
|
// movementManager.animateWindow(win, targets.listen.x, targets.listen.y);
|
||||||
|
|
||||||
|
// } else {
|
||||||
|
// const targets = layoutManager.getTargetBoundsForFeatureWindows({ listen: true, ask: true });
|
||||||
|
// if (!targets.listen || !targets.ask) return;
|
||||||
|
|
||||||
|
// const startListenPos = { x: targets.listen.x - ANIM_OFFSET_X, y: targets.listen.y };
|
||||||
|
// win.setBounds(startListenPos);
|
||||||
|
|
||||||
|
// win.show();
|
||||||
|
// fadeWindow(win, 0, 1);
|
||||||
|
// movementManager.animateWindow(otherWin, targets.ask.x, targets.ask.y);
|
||||||
|
// movementManager.animateWindow(win, targets.listen.x, targets.listen.y);
|
||||||
|
// }
|
||||||
|
// } else if (name === 'ask') {
|
||||||
|
// if (!isOtherWinVisible) {
|
||||||
|
// const targets = layoutManager.getTargetBoundsForFeatureWindows({ listen: false, ask: true });
|
||||||
|
// if (!targets.ask) return;
|
||||||
|
|
||||||
|
// const startPos = { x: targets.ask.x, y: targets.ask.y - ANIM_OFFSET_Y };
|
||||||
|
// win.setBounds(startPos);
|
||||||
|
// win.show();
|
||||||
|
// fadeWindow(win, 0, 1);
|
||||||
|
// movementManager.animateWindow(win, targets.ask.x, targets.ask.y);
|
||||||
|
|
||||||
|
// } else {
|
||||||
|
// const targets = layoutManager.getTargetBoundsForFeatureWindows({ listen: true, ask: true });
|
||||||
|
// if (!targets.listen || !targets.ask) return;
|
||||||
|
|
||||||
|
// const startAskPos = { x: targets.ask.x, y: targets.ask.y - ANIM_OFFSET_Y };
|
||||||
|
// win.setBounds(startAskPos);
|
||||||
|
|
||||||
|
// win.show();
|
||||||
|
// fadeWindow(win, 0, 1);
|
||||||
|
// movementManager.animateWindow(otherWin, targets.listen.x, targets.listen.y);
|
||||||
|
// movementManager.animateWindow(win, targets.ask.x, targets.ask.y);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// const currentBounds = win.getBounds();
|
||||||
|
// fadeWindow(
|
||||||
|
// win, 1, 0, 250,
|
||||||
|
// () => win.hide()
|
||||||
|
// );
|
||||||
|
// if (name === 'listen') {
|
||||||
|
// if (!isOtherWinVisible) {
|
||||||
|
// const targetX = currentBounds.x - ANIM_OFFSET_X;
|
||||||
|
// movementManager.animateWindow(win, targetX, currentBounds.y);
|
||||||
|
// } else {
|
||||||
|
// const targetX = currentBounds.x - ANIM_OFFSET_X;
|
||||||
|
// movementManager.animateWindow(win, targetX, currentBounds.y);
|
||||||
|
// }
|
||||||
|
// } else if (name === 'ask') {
|
||||||
|
// if (!isOtherWinVisible) {
|
||||||
|
// const targetY = currentBounds.y - ANIM_OFFSET_Y;
|
||||||
|
// movementManager.animateWindow(win, currentBounds.x, targetY);
|
||||||
|
// } else {
|
||||||
|
// const targetAskY = currentBounds.y - ANIM_OFFSET_Y;
|
||||||
|
// movementManager.animateWindow(win, currentBounds.x, targetAskY);
|
||||||
|
|
||||||
|
// const targets = layoutManager.getTargetBoundsForFeatureWindows({ listen: true, ask: false });
|
||||||
|
// if (targets.listen) {
|
||||||
|
// movementManager.animateWindow(otherWin, targets.listen.x, targets.listen.y);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
if (name === 'listen' || name === 'ask') {
|
if (name === 'listen' || name === 'ask') {
|
||||||
|
const win = windowPool.get(name);
|
||||||
const otherName = name === 'listen' ? 'ask' : 'listen';
|
const otherName = name === 'listen' ? 'ask' : 'listen';
|
||||||
const otherWin = windowPool.get(otherName);
|
const otherWin = windowPool.get(otherName);
|
||||||
const isOtherWinVisible = otherWin && !otherWin.isDestroyed() && otherWin.isVisible();
|
const isOtherWinVisible = otherWin && !otherWin.isDestroyed() && otherWin.isVisible();
|
||||||
@ -286,84 +493,47 @@ async function handleWindowVisibilityRequest(windowPool, layoutManager, movement
|
|||||||
const ANIM_OFFSET_X = 50;
|
const ANIM_OFFSET_X = 50;
|
||||||
const ANIM_OFFSET_Y = 20;
|
const ANIM_OFFSET_Y = 20;
|
||||||
|
|
||||||
|
const finalVisibility = {
|
||||||
|
listen: (name === 'listen' && shouldBeVisible) || (otherName === 'listen' && isOtherWinVisible),
|
||||||
|
ask: (name === 'ask' && shouldBeVisible) || (otherName === 'ask' && isOtherWinVisible),
|
||||||
|
};
|
||||||
|
if (!shouldBeVisible) {
|
||||||
|
finalVisibility[name] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetLayout = layoutManager.calculateFeatureWindowLayout(finalVisibility);
|
||||||
|
|
||||||
if (shouldBeVisible) {
|
if (shouldBeVisible) {
|
||||||
|
if (!win) return;
|
||||||
|
const targetBounds = targetLayout[name];
|
||||||
|
if (!targetBounds) return;
|
||||||
|
|
||||||
|
const startPos = { ...targetBounds };
|
||||||
|
if (name === 'listen') startPos.x -= ANIM_OFFSET_X;
|
||||||
|
else if (name === 'ask') startPos.y -= ANIM_OFFSET_Y;
|
||||||
|
|
||||||
win.setOpacity(0);
|
win.setOpacity(0);
|
||||||
|
|
||||||
if (name === 'listen') {
|
|
||||||
if (!isOtherWinVisible) {
|
|
||||||
const targets = layoutManager.getTargetBoundsForFeatureWindows({ listen: true, ask: false });
|
|
||||||
if (!targets.listen) return;
|
|
||||||
|
|
||||||
const startPos = { x: targets.listen.x - ANIM_OFFSET_X, y: targets.listen.y };
|
|
||||||
win.setBounds(startPos);
|
win.setBounds(startPos);
|
||||||
win.show();
|
win.show();
|
||||||
fadeWindow(win, 0, 1);
|
|
||||||
movementManager.animateWindow(win, targets.listen.x, targets.listen.y);
|
movementManager.fade(win, { to: 1 });
|
||||||
|
movementManager.animateLayout(targetLayout);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
const targets = layoutManager.getTargetBoundsForFeatureWindows({ listen: true, ask: true });
|
if (!win || !win.isVisible()) return;
|
||||||
if (!targets.listen || !targets.ask) return;
|
|
||||||
|
|
||||||
const startListenPos = { x: targets.listen.x - ANIM_OFFSET_X, y: targets.listen.y };
|
|
||||||
win.setBounds(startListenPos);
|
|
||||||
|
|
||||||
win.show();
|
|
||||||
fadeWindow(win, 0, 1);
|
|
||||||
movementManager.animateWindow(otherWin, targets.ask.x, targets.ask.y);
|
|
||||||
movementManager.animateWindow(win, targets.listen.x, targets.listen.y);
|
|
||||||
}
|
|
||||||
} else if (name === 'ask') {
|
|
||||||
if (!isOtherWinVisible) {
|
|
||||||
const targets = layoutManager.getTargetBoundsForFeatureWindows({ listen: false, ask: true });
|
|
||||||
if (!targets.ask) return;
|
|
||||||
|
|
||||||
const startPos = { x: targets.ask.x, y: targets.ask.y - ANIM_OFFSET_Y };
|
|
||||||
win.setBounds(startPos);
|
|
||||||
win.show();
|
|
||||||
fadeWindow(win, 0, 1);
|
|
||||||
movementManager.animateWindow(win, targets.ask.x, targets.ask.y);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
const targets = layoutManager.getTargetBoundsForFeatureWindows({ listen: true, ask: true });
|
|
||||||
if (!targets.listen || !targets.ask) return;
|
|
||||||
|
|
||||||
const startAskPos = { x: targets.ask.x, y: targets.ask.y - ANIM_OFFSET_Y };
|
|
||||||
win.setBounds(startAskPos);
|
|
||||||
|
|
||||||
win.show();
|
|
||||||
fadeWindow(win, 0, 1);
|
|
||||||
movementManager.animateWindow(otherWin, targets.listen.x, targets.listen.y);
|
|
||||||
movementManager.animateWindow(win, targets.ask.x, targets.ask.y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const currentBounds = win.getBounds();
|
const currentBounds = win.getBounds();
|
||||||
fadeWindow(
|
const targetPos = { ...currentBounds };
|
||||||
win, 1, 0, 250,
|
if (name === 'listen') targetPos.x -= ANIM_OFFSET_X;
|
||||||
() => win.hide()
|
else if (name === 'ask') targetPos.y -= ANIM_OFFSET_Y;
|
||||||
);
|
|
||||||
if (name === 'listen') {
|
|
||||||
if (!isOtherWinVisible) {
|
|
||||||
const targetX = currentBounds.x - ANIM_OFFSET_X;
|
|
||||||
movementManager.animateWindow(win, targetX, currentBounds.y);
|
|
||||||
} else {
|
|
||||||
const targetX = currentBounds.x - ANIM_OFFSET_X;
|
|
||||||
movementManager.animateWindow(win, targetX, currentBounds.y);
|
|
||||||
}
|
|
||||||
} else if (name === 'ask') {
|
|
||||||
if (!isOtherWinVisible) {
|
|
||||||
const targetY = currentBounds.y - ANIM_OFFSET_Y;
|
|
||||||
movementManager.animateWindow(win, currentBounds.x, targetY);
|
|
||||||
} else {
|
|
||||||
const targetAskY = currentBounds.y - ANIM_OFFSET_Y;
|
|
||||||
movementManager.animateWindow(win, currentBounds.x, targetAskY);
|
|
||||||
|
|
||||||
const targets = layoutManager.getTargetBoundsForFeatureWindows({ listen: true, ask: false });
|
movementManager.fade(win, { to: 0, onComplete: () => win.hide() });
|
||||||
if (targets.listen) {
|
movementManager.animateWindowPosition(win, targetPos);
|
||||||
movementManager.animateWindow(otherWin, targets.listen.x, targets.listen.y);
|
|
||||||
}
|
// 다른 창들도 새 레이아웃으로 애니메이션
|
||||||
}
|
const otherWindowsLayout = { ...targetLayout };
|
||||||
}
|
delete otherWindowsLayout[name];
|
||||||
|
movementManager.animateLayout(otherWindowsLayout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -661,16 +831,18 @@ function createWindows() {
|
|||||||
}
|
}
|
||||||
windowPool.set('header', header);
|
windowPool.set('header', header);
|
||||||
layoutManager = new WindowLayoutManager(windowPool);
|
layoutManager = new WindowLayoutManager(windowPool);
|
||||||
movementManager = new SmoothMovementManager(windowPool, layoutManager);
|
// movementManager = new SmoothMovementManager(windowPool, layoutManager);
|
||||||
|
movementManager = new SmoothMovementManager(windowPool);
|
||||||
|
|
||||||
header.on('moved', () => layoutManager.updateLayout());
|
// header.on('moved', () => layoutManager.updateLayout());
|
||||||
|
header.on('moved', () => updateChildWindowLayouts(false));
|
||||||
|
|
||||||
header.webContents.once('dom-ready', () => {
|
header.webContents.once('dom-ready', () => {
|
||||||
shortcutsService.initialize(windowPool);
|
shortcutsService.initialize(windowPool);
|
||||||
shortcutsService.registerShortcuts();
|
shortcutsService.registerShortcuts();
|
||||||
});
|
});
|
||||||
|
|
||||||
setupIpcHandlers(movementManager, layoutManager);
|
setupIpcHandlers(windowPool, layoutManager);
|
||||||
setupWindowController(windowPool, layoutManager, movementManager);
|
setupWindowController(windowPool, layoutManager, movementManager);
|
||||||
|
|
||||||
if (currentHeaderState === 'main') {
|
if (currentHeaderState === 'main') {
|
||||||
@ -702,16 +874,37 @@ function createWindows() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
header.on('resize', () => {
|
// header.on('resize', () => {
|
||||||
console.log('[WindowManager] Header resize event triggered');
|
// console.log('[WindowManager] Header resize event triggered');
|
||||||
layoutManager.updateLayout();
|
// layoutManager.updateLayout();
|
||||||
});
|
// });
|
||||||
|
header.on('resize', () => updateChildWindowLayouts(false));
|
||||||
|
|
||||||
return windowPool;
|
return windowPool;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupIpcHandlers(movementManager, layoutManager) {
|
// function setupIpcHandlers(movementManager, layoutManager) {
|
||||||
// quit-application handler moved to windowBridge.js to avoid duplication
|
// // quit-application handler moved to windowBridge.js to avoid duplication
|
||||||
|
// screen.on('display-added', (event, newDisplay) => {
|
||||||
|
// console.log('[Display] New display added:', newDisplay.id);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// screen.on('display-removed', (event, oldDisplay) => {
|
||||||
|
// console.log('[Display] Display removed:', oldDisplay.id);
|
||||||
|
// const header = windowPool.get('header');
|
||||||
|
// if (header && getCurrentDisplay(header).id === oldDisplay.id) {
|
||||||
|
// const primaryDisplay = screen.getPrimaryDisplay();
|
||||||
|
// movementManager.moveToDisplay(primaryDisplay.id);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
// screen.on('display-metrics-changed', (event, display, changedMetrics) => {
|
||||||
|
// // console.log('[Display] Display metrics changed:', display.id, changedMetrics);
|
||||||
|
// layoutManager.updateLayout();
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
function setupIpcHandlers(windowPool, layoutManager) {
|
||||||
screen.on('display-added', (event, newDisplay) => {
|
screen.on('display-added', (event, newDisplay) => {
|
||||||
console.log('[Display] New display added:', newDisplay.id);
|
console.log('[Display] New display added:', newDisplay.id);
|
||||||
});
|
});
|
||||||
@ -719,15 +912,21 @@ function setupIpcHandlers(movementManager, layoutManager) {
|
|||||||
screen.on('display-removed', (event, oldDisplay) => {
|
screen.on('display-removed', (event, oldDisplay) => {
|
||||||
console.log('[Display] Display removed:', oldDisplay.id);
|
console.log('[Display] Display removed:', oldDisplay.id);
|
||||||
const header = windowPool.get('header');
|
const header = windowPool.get('header');
|
||||||
|
|
||||||
if (header && getCurrentDisplay(header).id === oldDisplay.id) {
|
if (header && getCurrentDisplay(header).id === oldDisplay.id) {
|
||||||
const primaryDisplay = screen.getPrimaryDisplay();
|
const primaryDisplay = screen.getPrimaryDisplay();
|
||||||
movementManager.moveToDisplay(primaryDisplay.id);
|
const newPosition = layoutManager.calculateNewPositionForDisplay(header, primaryDisplay.id);
|
||||||
|
if (newPosition) {
|
||||||
|
// 복구 상황이므로 애니메이션 없이 즉시 이동
|
||||||
|
header.setPosition(newPosition.x, newPosition.y, false);
|
||||||
|
updateChildWindowLayouts(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
screen.on('display-metrics-changed', (event, display, changedMetrics) => {
|
screen.on('display-metrics-changed', (event, display, changedMetrics) => {
|
||||||
// console.log('[Display] Display metrics changed:', display.id, changedMetrics);
|
// 레이아웃 업데이트 함수를 새 버전으로 호출
|
||||||
layoutManager.updateLayout();
|
updateChildWindowLayouts(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -745,39 +944,39 @@ const handleHeaderStateChanged = (state) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const resizingHeaderWindow = (layoutManager, movementManager, { width, height }) => {
|
// const resizingHeaderWindow = (layoutManager, movementManager, { width, height }) => {
|
||||||
if (movementManager.isAnimating) {
|
// if (movementManager.isAnimating) {
|
||||||
console.log('[WindowManager] Skipping resize during animation');
|
// console.log('[WindowManager] Skipping resize during animation');
|
||||||
return { success: false, error: 'Cannot resize during animation' };
|
// return { success: false, error: 'Cannot resize during animation' };
|
||||||
}
|
// }
|
||||||
|
|
||||||
return layoutManager.resizeHeaderWindow({ width, height });
|
// return layoutManager.resizeHeaderWindow({ width, height });
|
||||||
};
|
// };
|
||||||
|
|
||||||
const handlingHeaderAnimationFinished = (windowPool, layoutManager, state) => {
|
// const handlingHeaderAnimationFinished = (windowPool, layoutManager, state) => {
|
||||||
const header = windowPool.get('header');
|
// const header = windowPool.get('header');
|
||||||
if (!header || header.isDestroyed()) return;
|
// if (!header || header.isDestroyed()) return;
|
||||||
|
|
||||||
if (state === 'hidden') {
|
// if (state === 'hidden') {
|
||||||
header.hide();
|
// header.hide();
|
||||||
console.log('[WindowManager] Header hidden after animation.');
|
// console.log('[WindowManager] Header hidden after animation.');
|
||||||
} else if (state === 'visible') {
|
// } else if (state === 'visible') {
|
||||||
console.log('[WindowManager] Header shown after animation.');
|
// console.log('[WindowManager] Header shown after animation.');
|
||||||
layoutManager.updateLayout();
|
// layoutManager.updateLayout();
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
const gettingHeaderPosition = (layoutManager) => {
|
// const gettingHeaderPosition = (layoutManager) => {
|
||||||
return layoutManager.getHeaderPosition();
|
// return layoutManager.getHeaderPosition();
|
||||||
};
|
// };
|
||||||
|
|
||||||
const movingHeaderTo = (layoutManager, newX, newY) => {
|
// const movingHeaderTo = (layoutManager, newX, newY) => {
|
||||||
layoutManager.moveHeaderTo(newX, newY);
|
// layoutManager.moveHeaderTo(newX, newY);
|
||||||
};
|
// };
|
||||||
|
|
||||||
const adjustingWindowHeight = (layoutManager, sender, targetHeight) => {
|
// const adjustingWindowHeight = (layoutManager, sender, targetHeight) => {
|
||||||
layoutManager.adjustWindowHeight(sender, targetHeight);
|
// layoutManager.adjustWindowHeight(sender, targetHeight);
|
||||||
};
|
// };
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user