From 3bba5af340d01f269dd9d1ecc1b5913f423c9b72 Mon Sep 17 00:00:00 2001 From: abdussamedulutas Date: Wed, 17 Jun 2026 13:31:09 +0300 Subject: [PATCH] =?UTF-8?q?Studio:=20ID=20kart=C4=B1,=20modal,=20oda=20olu?= =?UTF-8?q?=C5=9Fturma,=20Ana=20butonu=20kald=C4=B1r=C4=B1ld=C4=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Araç çubuğu: - Socket ID → kopyalanabilir kart (tıkla → clipboard, yeşil flash) - "Ana" butonu kaldırıldı (kafakarıştırıcıydı, Miller kolon kendisi navigasyon sağlıyor) - Durum mesajı sağda kaldı Modal sistemi (_showModal): - Başlık + label'lı input alanları + İptal/Tamam butonları - Arka plana tıkla veya ✕ ile kapat - Enter → onayla, Esc → kapat - İlk alana otomatik focus Eşler kolonu: - "ID ile ara" → prompt() yerine modal - Alan: Socket ID, placeholder: 'xxxxxxxx-xxxx-…' Odalar kolonu: - "Oda Oluştur" (primary buton) → modal - Alanlar: oda adı + opsiyonel şifre - Oluşturunca oda kolonunu yeniler style.css: - .mwse-id-card + __label/__value/__copy + --copied - .mwse-modal-overlay / .mwse-modal / __header/__body/__field/__footer/__input/__close Co-Authored-By: Claude Sonnet 4.6 --- public/studio/Studio.js | 184 ++++++++++++++++++++++++++++++++++------ public/studio/style.css | 136 +++++++++++++++++++++++++++++ 2 files changed, 295 insertions(+), 25 deletions(-) diff --git a/public/studio/Studio.js b/public/studio/Studio.js index c6f1b4e..5d2cbb3 100644 --- a/public/studio/Studio.js +++ b/public/studio/Studio.js @@ -50,34 +50,54 @@ export default class Studio { const bar = document.createElement('div'); bar.className = 'mwse-studio__toolbar'; + // Logo const logo = document.createElement('span'); logo.className = 'mwse-studio__title'; logo.innerHTML = 'MWSE Studio'; bar.appendChild(logo); - const idEl = document.createElement('span'); - idEl.className = 'mwse-studio__status'; - idEl.style.fontFamily = 'monospace'; - idEl.textContent = '…'; - this.mwse.me.on('scope', () => { idEl.textContent = this.mwse.me.socketId; }); - bar.appendChild(idEl); + // Benim ID kartı — kopyalanabilir + const idCard = document.createElement('div'); + idCard.className = 'mwse-id-card'; + idCard.title = 'Socket ID\'yi kopyala'; + const idLabel = document.createElement('span'); + idLabel.className = 'mwse-id-card__label'; + idLabel.textContent = 'ID'; + + const idValue = document.createElement('span'); + idValue.className = 'mwse-id-card__value'; + idValue.textContent = '…'; + + const idCopy = document.createElement('span'); + idCopy.className = 'mwse-id-card__copy'; + idCopy.textContent = '⎘'; + + idCard.append(idLabel, idValue, idCopy); + idCard.addEventListener('click', () => { + const id = this.mwse.me.socketId; + if (!id || id === '…') return; + navigator.clipboard.writeText(id).then(() => { + idCopy.textContent = '✓'; + idCard.classList.add('mwse-id-card--copied'); + setTimeout(() => { + idCard.classList.remove('mwse-id-card--copied'); + idCopy.textContent = '⎘'; + }, 1800); + }); + }); + + this.mwse.me.on('scope', () => { + idValue.textContent = this.mwse.me.socketId; + }); + bar.appendChild(idCard); + + // Durum mesajı this._statusEl = document.createElement('span'); this._statusEl.className = 'mwse-studio__status mwse-studio__status--online'; this._statusEl.textContent = 'Bağlı'; bar.appendChild(this._statusEl); - // Kök'e dönme butonu - const homeBtn = document.createElement('button'); - homeBtn.className = 'mwse-btn'; - homeBtn.style.cssText = 'margin-left:auto;padding:3px 10px;font-size:11px'; - homeBtn.textContent = '⌂ Ana'; - homeBtn.addEventListener('click', () => { - this._view.popTo(0); - this._pushRootColumn(); - }); - bar.appendChild(homeBtn); - this._el.insertBefore(bar, this._el.firstChild); } @@ -139,11 +159,19 @@ export default class Studio { const col = this._view.pushColumn('Eşler', items); col.addAction('ID ile ara', '', () => { - const id = prompt('Eş socket ID:'); - if (!id?.trim()) return; - this.mwse.peer(id.trim()).requestPair() - .then(() => this._setStatus('', `${id.slice(-8)}'e istek gönderildi`)) - .catch(e => this._setStatus('error', e.message)); + this._showModal({ + title: 'Eşe bağlan', + fields: [ + { key: 'id', label: 'Socket ID', placeholder: 'xxxxxxxx-xxxx-…' } + ], + confirm: 'Bağlan', + onConfirm: ({ id }) => { + if (!id) return; + this.mwse.peer(id).requestPair() + .then(() => this._setStatus('', `${id.slice(-8)}'e istek gönderildi`)) + .catch(e => this._setStatus('error', e.message)); + } + }); }); } @@ -246,12 +274,38 @@ export default class Studio { if (!items.length) { items.push({ - icon: '—', label: 'Oda yok', - meta: 'room.createRoom() ile oluştur', hasChildren: false + icon: '—', label: 'Oda yok', meta: 'Aşağıdan oluştur', hasChildren: false }); } - this._view.pushColumn('Odalar', items); + const col = this._view.pushColumn('Odalar', items); + col.addAction('Oda Oluştur', 'mwse-btn--primary', () => { + this._showModal({ + title: 'Yeni Oda', + fields: [ + { key: 'name', label: 'Oda adı', placeholder: 'genel' }, + { key: 'pass', label: 'Şifre (opsiyonel)', placeholder: 'boş bırak = şifresiz' } + ], + confirm: 'Oluştur', + onConfirm: async ({ name, pass }) => { + if (!name) return; + const room = this.mwse.room({ + name, + joinType: pass ? 'password' : 'free', + accessType: 'public', + ifexistsJoin: true, + notifyActionJoined: true, + notifyActionEjected: true, + notifyActionInvite: false, + ...(pass ? { password: pass } : {}) + }); + await room.createRoom(); + this._setStatus('online', `"${name}" odası oluşturuldu`); + this._view.popTo(1); + this._pushRoomsColumn(); + } + }); + }); } _pushRoomMembersColumn(room, roomId) { @@ -499,6 +553,86 @@ export default class Studio { this._statusEl.textContent = text; } + // ── Modal ───────────────────────────────────────────────────────────────── + // Kullanım: this._showModal({ title, fields:[{key,label,placeholder}], confirm, onConfirm }) + + _showModal({ title, fields = [], confirm = 'Tamam', onConfirm }) { + const overlay = document.createElement('div'); + overlay.className = 'mwse-modal-overlay'; + + const modal = document.createElement('div'); + modal.className = 'mwse-modal'; + + // Başlık + const header = document.createElement('div'); + header.className = 'mwse-modal__header'; + const titleEl = document.createElement('span'); + titleEl.textContent = title; + const closeBtn = document.createElement('span'); + closeBtn.className = 'mwse-modal__close'; + closeBtn.textContent = '✕'; + closeBtn.addEventListener('click', () => overlay.remove()); + header.append(titleEl, closeBtn); + + // Gövde — alanlar + const body = document.createElement('div'); + body.className = 'mwse-modal__body'; + const inputs = {}; + + for (const f of fields) { + const wrap = document.createElement('div'); + wrap.className = 'mwse-modal__field'; + + const lbl = document.createElement('label'); + lbl.textContent = f.label; + + const inp = document.createElement('input'); + inp.className = 'mwse-modal__input'; + inp.placeholder = f.placeholder ?? ''; + inp.type = f.type ?? 'text'; + inputs[f.key] = inp; + + inp.addEventListener('keydown', e => { + if (e.key === 'Enter') confirmBtn.click(); + if (e.key === 'Escape') overlay.remove(); + }); + + wrap.append(lbl, inp); + body.appendChild(wrap); + } + + // Alt butonlar + const footer = document.createElement('div'); + footer.className = 'mwse-modal__footer'; + + const cancelBtn = document.createElement('button'); + cancelBtn.className = 'mwse-btn'; + cancelBtn.textContent = 'İptal'; + cancelBtn.addEventListener('click', () => overlay.remove()); + + const confirmBtn = document.createElement('button'); + confirmBtn.className = 'mwse-btn mwse-btn--primary'; + confirmBtn.textContent = confirm; + confirmBtn.addEventListener('click', () => { + const values = {}; + for (const [k, el] of Object.entries(inputs)) values[k] = el.value.trim(); + overlay.remove(); + onConfirm(values); + }); + + footer.append(cancelBtn, confirmBtn); + modal.append(header, body, footer); + overlay.appendChild(modal); + document.body.appendChild(overlay); + + // Arka plana tıkla → kapat + overlay.addEventListener('click', e => { if (e.target === overlay) overlay.remove(); }); + + // İlk alana odaklan + const first = Object.values(inputs)[0]; + if (first) setTimeout(() => first.focus(), 50); + } + _injectStyle() { if (this._styleOK) return; const link = document.createElement('link'); diff --git a/public/studio/style.css b/public/studio/style.css index e8c2a37..1667d4c 100644 --- a/public/studio/style.css +++ b/public/studio/style.css @@ -253,3 +253,139 @@ border-top: 1px solid #2a2a2a; object-fit: contain; } + +/* ---- Benim ID kartı ---- */ +.mwse-id-card { + display: flex; + align-items: center; + gap: 6px; + padding: 3px 8px; + background: #252525; + border: 1px solid #333; + border-radius: 4px; + cursor: pointer; + transition: border-color 150ms; + max-width: 260px; + overflow: hidden; +} +.mwse-id-card:hover { border-color: #0078d4; } + +.mwse-id-card__label { + font-size: 10px; + color: #666; + text-transform: uppercase; + letter-spacing: .05em; + flex-shrink: 0; +} + +.mwse-id-card__value { + font-family: 'Consolas', 'Menlo', monospace; + font-size: 11px; + color: #b0b0b0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + flex: 1; +} + +.mwse-id-card__copy { + font-size: 11px; + color: #555; + flex-shrink: 0; + transition: color 150ms; +} +.mwse-id-card:hover .mwse-id-card__copy { color: #0078d4; } + +.mwse-id-card--copied { + border-color: #4caf50 !important; +} +.mwse-id-card--copied .mwse-id-card__value { color: #4caf50; } +.mwse-id-card--copied .mwse-id-card__copy { color: #4caf50; } + +/* ---- Modal ---- */ +.mwse-modal-overlay { + position: fixed; + inset: 0; + background: rgba(0,0,0,.65); + display: flex; + align-items: center; + justify-content: center; + z-index: 9999; +} + +.mwse-modal { + background: #1e1e1e; + border: 1px solid #3a3a3a; + border-radius: 6px; + width: 360px; + max-width: calc(100vw - 32px); + box-shadow: 0 8px 32px rgba(0,0,0,.5); + display: flex; + flex-direction: column; +} + +.mwse-modal__header { + padding: 12px 16px 10px; + border-bottom: 1px solid #2a2a2a; + font-weight: 600; + font-size: 13px; + color: #e0e0e0; + display: flex; + align-items: center; + justify-content: space-between; +} + +.mwse-modal__close { + cursor: pointer; + color: #555; + font-size: 16px; + line-height: 1; + padding: 2px 4px; + border-radius: 3px; +} +.mwse-modal__close:hover { background: #2a2a2a; color: #ccc; } + +.mwse-modal__body { + padding: 16px; + display: flex; + flex-direction: column; + gap: 10px; +} + +.mwse-modal__field { + display: flex; + flex-direction: column; + gap: 4px; +} + +.mwse-modal__field label { + font-size: 11px; + color: #888; + letter-spacing: .03em; +} + +.mwse-modal__input { + width: 100%; + padding: 7px 10px; + background: #2a2a2a; + border: 1px solid #3a3a3a; + border-radius: 4px; + color: #d4d4d4; + font-size: 13px; + outline: none; + box-sizing: border-box; +} +.mwse-modal__input:focus { border-color: #0078d4; } + +.mwse-modal__footer { + padding: 10px 16px 14px; + display: flex; + gap: 8px; + justify-content: flex-end; +} + +.mwse-modal__footer .mwse-btn { + flex: none; + padding: 6px 18px; + font-size: 12px; +}