Numex yayın: public/js/windowManager.js
This commit is contained in:
parent
ea56378029
commit
5601106ba5
322
public/js/windowManager.js
Normal file
322
public/js/windowManager.js
Normal file
|
|
@ -0,0 +1,322 @@
|
|||
/* ============================================================
|
||||
AURA OS — Pencere Yöneticisi (Window Manager)
|
||||
createWindow / focus / minimize / restore / close / drag / z-sırası
|
||||
============================================================ */
|
||||
|
||||
// Global uygulama kayıt defteri
|
||||
const AuraApps = {};
|
||||
|
||||
// Pencere yöneticisi durumu
|
||||
const Wm = {
|
||||
windows: [], // tüm pencere kayıtları
|
||||
zCounter: 10, // z-index sayaç (her focus artar)
|
||||
activeWin: null, // şu an üstte olan pencere
|
||||
nextId: 1,
|
||||
};
|
||||
|
||||
function registerApp(appId, config) {
|
||||
AuraApps[appId] = config;
|
||||
// console.log('[AuraApps] kayıtlı:', appId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Yeni bir pencere oluşturup ekran katmanına ekler.
|
||||
* @param {object} opts
|
||||
* appId: string — uygulama kimliği (AuraApps kaydı)
|
||||
* title: string — başlık
|
||||
* icon: string — emoji ikon
|
||||
* width,height: pencere boyut
|
||||
* x, y: başlangıç konumu (opsiyonel)
|
||||
* content: html string (body içeriği)
|
||||
* onReady: function(winBody) — içeriğin DOM'a bağlanmasından sonra çağrılır
|
||||
*/
|
||||
function createWindow(opts) {
|
||||
const app = AuraApps[opts.appId];
|
||||
if (!app) {
|
||||
console.error('[WM] Bilinmeyen uygulama:', opts.appId);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Varsayılanlar
|
||||
const width = opts.width || app.defaultWidth || 560;
|
||||
const height = opts.height || app.defaultHeight || 400;
|
||||
|
||||
// Konum: verilmediyse kademeli merkez açılım
|
||||
const vw = window.innerWidth;
|
||||
const vh = window.innerHeight - 50; // taskbar payı
|
||||
const baseX = typeof opts.x === 'number' ? opts.x : Math.max(30, Math.round((vw - width) / 2));
|
||||
const baseY = typeof opts.y === 'number' ? opts.y : Math.max(20, Math.round((vh - height) / 3));
|
||||
const offset = (Wm.windows.length % 8) * 22;
|
||||
const x = Math.min(baseX + offset, vw - width - 20);
|
||||
const y = Math.min(baseY + offset, vh - height - 20);
|
||||
|
||||
const id = 'win-' + (Wm.nextId++);
|
||||
const zIndex = ++Wm.zCounter;
|
||||
|
||||
// Pencere kök elemanı
|
||||
const win = document.createElement('div');
|
||||
win.className = 'aura-window active';
|
||||
win.id = id;
|
||||
win.dataset.appId = opts.appId;
|
||||
win.style.left = Math.max(6, x) + 'px';
|
||||
win.style.top = Math.max(6, y) + 'px';
|
||||
win.style.width = width + 'px';
|
||||
win.style.height = height + 'px';
|
||||
win.style.zIndex = zIndex;
|
||||
|
||||
// Baslık çubuğu
|
||||
const titlebar = document.createElement('div');
|
||||
titlebar.className = 'window-titlebar';
|
||||
titlebar.innerHTML =
|
||||
'<span class="win-icon">' + (opts.icon || app.icon || '🪟') + '</span>' +
|
||||
'<span class="win-title">' + escapeHtml(opts.title || app.name || opts.appId) + '</span>' +
|
||||
'<div class="win-btns">' +
|
||||
'<button class="win-btn btn-min" title="Küçült">—</button>' +
|
||||
'<button class="win-btn btn-close" title="Kapat">×</button>' +
|
||||
'</div>';
|
||||
|
||||
// Gövde
|
||||
const body = document.createElement('div');
|
||||
body.className = 'window-body';
|
||||
|
||||
win.appendChild(titlebar);
|
||||
win.appendChild(body);
|
||||
|
||||
// Kayıt nesnesi
|
||||
const record = {
|
||||
id,
|
||||
appId: opts.appId,
|
||||
title: opts.title || app.name,
|
||||
icon: opts.icon || app.icon,
|
||||
el: win,
|
||||
titlebar,
|
||||
body,
|
||||
minimized: false,
|
||||
metadata: opts.metadata || null,
|
||||
};
|
||||
|
||||
Wm.windows.push(record);
|
||||
|
||||
// Pencereyi katmana ekle
|
||||
const layer = document.getElementById('window-layer');
|
||||
layer.appendChild(win);
|
||||
record.launchTime = Date.now();
|
||||
|
||||
// ---- Başlık çubuğundan sürükleme ----
|
||||
setupDrag(record);
|
||||
|
||||
// ---- Odaklama olayı: pencereye tıklandığında en öne ----
|
||||
win.addEventListener('mousedown', (e) => {
|
||||
if (record.minimized) return;
|
||||
focusWindow(record.id);
|
||||
});
|
||||
// Başlık çubuğu drag başlarken de öne alınmalı
|
||||
titlebar.addEventListener('mousedown', (e) => {
|
||||
if (record.minimized) return;
|
||||
focusWindow(record.id);
|
||||
});
|
||||
|
||||
// ---- Buton davranışları ----
|
||||
const minBtn = titlebar.querySelector('.btn-min');
|
||||
const closeBtn = titlebar.querySelector('.btn-close');
|
||||
minBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
minimizeWindow(record.id);
|
||||
});
|
||||
closeBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
closeWindow(record.id);
|
||||
});
|
||||
|
||||
// ---- İçeriği doldur ----
|
||||
if (typeof opts.content === 'string') {
|
||||
body.innerHTML = opts.content;
|
||||
}
|
||||
|
||||
// ---- Görev çubuğu butonunu oluştur ----
|
||||
window.Taskbar && window.Taskbar.addTaskButton(record);
|
||||
record.taskBtn = window.Taskbar && window.Taskbar.getTaskBtn(record.id);
|
||||
|
||||
// ---- Uygulamanın kurulum çağrısı ----
|
||||
if (app.onLaunch) {
|
||||
try {
|
||||
app.onLaunch(record, body);
|
||||
} catch (err) {
|
||||
console.error('[WM] onLaunch hatası:', err);
|
||||
body.innerHTML = '<div class="note-item"><div class="ni-empty">Hata: ' + escapeHtml(err.message) + '</div></div>';
|
||||
}
|
||||
}
|
||||
|
||||
// ---- onReady çağrısı ----
|
||||
if (typeof opts.onReady === 'function') {
|
||||
opts.onReady(record, body);
|
||||
}
|
||||
|
||||
window.Taskbar && window.Taskbar.render();
|
||||
focusWindow(record.id);
|
||||
|
||||
return { record, bodyEl: body, win };
|
||||
}
|
||||
|
||||
/* ------------ Saydam yardımcı: DOM'a sağlam pencere dış IP arayüzü ------------
|
||||
createWindow genel (global) fonksiyon olarak dönecek, alt-scriptler kullanır.
|
||||
Diğer modüller window.createWindow ≠ çalışmasın diye: */
|
||||
window.createWindow = createWindow;
|
||||
|
||||
/* ---- Odak (z-sırası) Yönetimi ---- */
|
||||
function focusWindow(id) {
|
||||
const rec = getWindow(id);
|
||||
if (!rec) return;
|
||||
|
||||
// Zaten aktifse ama minimize ise restore olur
|
||||
if (rec.minimized) return; // taskbar üzerinden restore edilir
|
||||
|
||||
// Aktif pencere sınıfını temizle
|
||||
Wm.windows.forEach(w => {
|
||||
w.el.classList.remove('active');
|
||||
w.taskBtn && w.taskBtn.classList.remove('active');
|
||||
});
|
||||
|
||||
rec.el.classList.add('active');
|
||||
rec.el.style.zIndex = ++Wm.zCounter;
|
||||
Wm.activeWin = rec;
|
||||
|
||||
if (recordBtnById(id)) recordBtnById(id).classList.add('active');
|
||||
rec.taskBtn && rec.taskBtn.classList.add('active');
|
||||
|
||||
window.Taskbar && window.Taskbar.render();
|
||||
// Pencereyi minimum boyutun üzerinde tutmaz, minimize restore
|
||||
if (rec.minimized && rec.__doRestore) topLevelRestore(rec);
|
||||
}
|
||||
|
||||
function topLevelRestore(rec) {
|
||||
rec.el.classList.remove('minimized');
|
||||
rec.minimized = false;
|
||||
rec.el.style.display = 'flex';
|
||||
}
|
||||
|
||||
/* ------------ Küçült ------------ */
|
||||
function minimizeWindow(id) {
|
||||
const rec = getWindow(id);
|
||||
if (!rec) return;
|
||||
rec.el.classList.add('minimized');
|
||||
rec.minimized = true;
|
||||
rec.el.classList.remove('active');
|
||||
rec.taskBtn && rec.taskBtn.classList.remove('active');
|
||||
|
||||
// Aktifliği bırak
|
||||
if (Wm.activeWin && Wm.activeWin.id === id) {
|
||||
Wm.activeWin = null;
|
||||
// Aktifliği diğer pencereye aktar
|
||||
const others = Wm.windows.filter(w => !w.minimized);
|
||||
if (others.length) focusWindow(others[others.length - 1].id);
|
||||
}
|
||||
window.Taskbar && window.Taskbar.render();
|
||||
}
|
||||
|
||||
/* ------------ Geri getir (restore) ------------ */
|
||||
function restoreWindow(id) {
|
||||
const rec = getWindow(id);
|
||||
if (!rec) return;
|
||||
rec.el.classList.remove('minimized');
|
||||
rec.minimized = false;
|
||||
rec.el.classList.add('active');
|
||||
rec.el.style.zIndex = ++Wm.zCounter;
|
||||
Wm.activeWin = rec;
|
||||
focusWindow(id);
|
||||
window.Taskbar && window.Taskbar.render();
|
||||
}
|
||||
|
||||
/* ------------ Kapat ------------ */
|
||||
function closeWindow(id) {
|
||||
const rec = getWindow(id);
|
||||
if (!rec) return;
|
||||
|
||||
const idx = Wm.windows.indexOf(rec);
|
||||
if (idx >= 0) Wm.windows.splice(idx, 1);
|
||||
|
||||
// Görev çubuğu butonunu kaldır
|
||||
if (window.Taskbar) {
|
||||
window.Taskbar.removeTaskButton(id);
|
||||
}
|
||||
|
||||
if (rec.el.parentNode) rec.el.parentNode.removeChild(rec.el);
|
||||
|
||||
if (Wm.activeWin && Wm.activeWin.id === id) {
|
||||
Wm.activeWin = null;
|
||||
const others = Wm.windows.filter(w => !w.minimized);
|
||||
if (others.length) focusWindow(others[others.length - 1].id);
|
||||
}
|
||||
window.Taskbar && window.Taskbar.render();
|
||||
}
|
||||
|
||||
/* ------------ Yardımcı fonksiyonlar ------------ */
|
||||
function getWindow(id) {
|
||||
return Wm.windows.find(w => w.id === id) || null;
|
||||
}
|
||||
|
||||
function recordBtnById(id) {
|
||||
const rec = getWindow(id);
|
||||
return rec && rec.taskBtn;
|
||||
}
|
||||
|
||||
function escapeHtml(str) {
|
||||
if (!str) return '';
|
||||
return String(str)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
/* ------------ Fareyle sürükleme (drag) ------------ */
|
||||
function setupDrag(rec) {
|
||||
const titlebar = rec.titlebar;
|
||||
let isDragging = false;
|
||||
let startX, startY, origX, origY, origPointerX, origPointerY;
|
||||
|
||||
titlebar.addEventListener('mousedown', (e) => {
|
||||
// Sol tuş; başlık butonlarına tıklanmışsa sürükleme başlatma
|
||||
if (e.button !== 0) return;
|
||||
if (e.target.closest('.win-btns')) return;
|
||||
|
||||
isDragging = true;
|
||||
const rect = rec.el.getBoundingClientRect();
|
||||
startX = rect.left;
|
||||
startY = rect.top;
|
||||
origPointerX = e.clientX;
|
||||
origPointerY = e.clientY;
|
||||
|
||||
rec.el.style.transition = 'none';
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
document.addEventListener('mousemove', (e) => {
|
||||
if (!isDragging) return;
|
||||
const dx = e.clientX - origPointerX;
|
||||
const dy = e.clientY - origPointerY;
|
||||
let nx = startX + dx;
|
||||
let ny = startY + dy;
|
||||
|
||||
// Ekran sınırları içinde tut (kısmen taşmasın)
|
||||
const vw = window.innerWidth;
|
||||
const vh = window.innerHeight - 50;
|
||||
nx = Math.min(nx, vw - 60);
|
||||
ny = Math.min(ny, vh - 30);
|
||||
nx = Math.max(nx, -rec.el.offsetWidth + 80);
|
||||
ny = Math.max(ny, 0);
|
||||
|
||||
rec.el.style.left = nx + 'px';
|
||||
rec.el.style.top = ny + 'px';
|
||||
});
|
||||
|
||||
document.addEventListener('mouseup', () => {
|
||||
if (isDragging) {
|
||||
isDragging = false;
|
||||
rec.el.style.transition = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* ----- Pencere sırasını gösteren API (debug/taskbar) ----- */
|
||||
window.Wm = Wm;
|
||||
Loading…
Reference in New Issue
Block a user