diff --git a/src/admin/admin.css b/src/admin/admin.css index c9a11fb2..b7da4c61 100644 --- a/src/admin/admin.css +++ b/src/admin/admin.css @@ -299,7 +299,8 @@ body { .adv-toggle input:checked + .track::after { left: 15px; background: #fff; } .sections { - flex: 1; min-height: 0; overflow-y: auto; padding: 16px 20px 26px; + flex: 1; min-height: 0; min-width: 0; overflow-y: auto; overflow-x: hidden; + padding: 16px 20px 26px; display: grid; grid-template-columns: repeat(auto-fill, minmax(330px, 1fr)); gap: 14px; align-content: start; } @@ -314,6 +315,10 @@ body { background: linear-gradient(180deg, var(--card), rgba(16,11,18,.9)); border: 1px solid var(--line); border-radius: var(--radius); padding: 14px 16px 15px; align-self: start; + /* min-width: auto ızgarayı etiketin boyuna açıp yatay kaydırma çıkarıyordu. */ + min-width: 0; + /* 4 px satır ızgarası kartı içeriğinden kısa sanmasın (Linux). */ + height: max-content; transition: border-color .2s, box-shadow .2s; } .card.wide { grid-column: 1 / -1; } diff --git a/src/admin/admin.js b/src/admin/admin.js index fa5ea4c3..fd05ce30 100644 --- a/src/admin/admin.js +++ b/src/admin/admin.js @@ -2710,7 +2710,9 @@ icon: 'sliders', wide: true, title: 'Genel Işık Ayarları', - desc: 'Mod, renk ve ses tepkisi — Windows Dynamic Lighting ve OpenRGB ortak görünümü. Her ayarın hangi çıkışlarda geçerli olduğu yanında yazar. Art-Net kendi kartındaki ayarları kullanır.', + desc: isWindows() + ? 'Mod, renk ve ses tepkisi — Windows Dynamic Lighting ve OpenRGB ortak görünümü. Her ayarın hangi çıkışlarda geçerli olduğu yanında yazar. Art-Net kendi kartındaki ayarları kullanır.' + : 'Mod, renk ve ses tepkisi — OpenRGB çıkışı. Art-Net kendi kartındaki ayarları kullanır.', roots: ['lighting'], rootOmit: ['lighting.enabled', 'lighting.deviceColors', 'lighting.deviceLedColors'], controls: [{ type: 'lightinggeneralpanel' }], @@ -3181,6 +3183,9 @@ } else { prevScroll = root.scrollTop; } + /* Masonry sınıfı bir önceki çizimden kalırsa yeni kartlar 4 px satıra + doğar. Linux o boyu içerik sanıp bütün kartları üst üste bindiriyor. */ + root.classList.remove('masonry'); root.innerHTML = ''; const cat = CATEGORIES.find((c) => c.id === activeCategory) || CATEGORIES[0]; @@ -3232,6 +3237,31 @@ ölçülüyor. Yalnız kalan yarım kart tam genişlik alıyor. */ const MASONRY_ROW = 4; // px — admin.css `.sections.masonry` ile aynı let masonryRO = null; + let sectionsRO = null; + let sectionsW = 0; + + function columnCount(root) { + const tpl = getComputedStyle(root).gridTemplateColumns; + if (!tpl || tpl === 'none') return 1; + return tpl.split(/\s+/).filter(Boolean).length || 1; + } + + /* Sahne Üretici ile MilkDrop Preset Üretici iki tam genişlik kartın + arasında. Pencere büyüyünce ızgara yeni sütun açar, bu ikisi birer + sütunda dar kalır. Sütun sayısı 2'yi geçince satırı yarım yarım paylaşırlar. */ + function placeGeneratorPair(root) { + const a = root.querySelector(':scope > [data-card="scenegen"]'); + const b = root.querySelector(':scope > [data-card="mdgen"]'); + if (!a || !b) return; + a.style.gridColumn = ''; + b.style.gridColumn = ''; + const cols = columnCount(root); + if (cols < 3) return; + const mid = Math.ceil(cols / 2); + a.style.gridColumn = '1 / ' + (mid + 1); + b.style.gridColumn = (mid + 1) + ' / -1'; + } + function layoutCards(root) { const kids = [...root.children]; let run = []; @@ -3242,19 +3272,62 @@ else run.push(c); } flush(); - root.classList.add('masonry'); + placeGeneratorPair(root); const gap = parseFloat(getComputedStyle(root).columnGap) || 14; - const span = (c) => { - const h = c.getBoundingClientRect().height; - const s = 'span ' + Math.max(1, Math.ceil((h + gap) / MASONRY_ROW)); + const spanFor = (h) => 'span ' + Math.max(1, Math.ceil((h + gap) / MASONRY_ROW)); + /* İçerik boyu, 4 px'lik satır ızgarası kapalıyken okunur. Sınıf açıkken + Linux getBoundingClientRect'i satır yüksekliği sanıyor; span 1 kalınca + kartlar aynı yere yığılıyor. Çalan parça gibi yeniden çizen her işlem + bu yoldan geçer, o yüzden ölçüm her seferinde doğal düzende yapılır. */ + root.classList.remove('masonry'); + const heights = kids.map((c) => c.getBoundingClientRect().height); + root.classList.add('masonry'); + kids.forEach((c, i) => { + const s = spanFor(heights[i]); if (c.style.gridRowEnd !== s) c.style.gridRowEnd = s; - }; - kids.forEach(span); + }); if (masonryRO) masonryRO.disconnect(); if (window.ResizeObserver) { - masonryRO = new ResizeObserver((entries) => { for (const e of entries) span(e.target); }); + masonryRO = new ResizeObserver((entries) => { + for (const e of entries) { + const c = e.target; + if (!c.isConnected) continue; + const h = Math.max(c.scrollHeight, c.getBoundingClientRect().height); + const s = spanFor(h); + const cur = parseInt(String(c.style.gridRowEnd).replace(/[^\d]/g, ''), 10) || 0; + const next = parseInt(s.replace(/[^\d]/g, ''), 10) || 0; + /* Küçültme tam yeniden çizime bırakılır; gözlemci yalnızca + büyüyen içeriği (yüklenen kapak gibi) takip eder. Aksi halde + 4 px satır ölçümü span'i eritip kartları üst üste bindirir. */ + if (next > cur) c.style.gridRowEnd = s; + } + }); kids.forEach((c) => masonryRO.observe(c)); } + /* Genişlik değişince payı aynı karede yenile. Sütun sayısı aynı kalsa + da eski grid-column çizgileri yatay kaydırmayı bir sonraki çizime + kadar açık bırakıyordu. */ + if (sectionsRO) sectionsRO.disconnect(); + sectionsW = root.clientWidth; + if (window.ResizeObserver) { + sectionsRO = new ResizeObserver(() => { + if (!root.isConnected) return; + const w = root.clientWidth; + if (w === sectionsW) return; + sectionsW = w; + placeGeneratorPair(root); + root.classList.remove('masonry'); + const again = [...root.children]; + const hs = again.map((c) => c.getBoundingClientRect().height); + root.classList.add('masonry'); + again.forEach((c, i) => { + const s = spanFor(hs[i]); + if (c.style.gridRowEnd !== s) c.style.gridRowEnd = s; + }); + sectionsW = root.clientWidth; + }); + sectionsRO.observe(root); + } } // Tek bir kart: başlık + gruplanmış kontroller (+ gelişmiş) diff --git a/src/admin/lighting-general.js b/src/admin/lighting-general.js index 1526ffbc..c8a216a7 100644 --- a/src/admin/lighting-general.js +++ b/src/admin/lighting-general.js @@ -3,7 +3,9 @@ * * Eskiden bu ayarlar yalnız WDL kartı AÇIKken görünüyordu; OpenRGB tek başına * açıkken veya henüz hiçbir çıkış seçilmemişken dokunulamıyordu. Bu panel - * Işık kategorisinin en altında her zaman durur. + * Işık kategorisinin en altında her zaman durur. Linux ve macOS'ta kart + * kapanmaz: yalnız WDL'ye ait denetimler ve "Windows Dynamic Lighting" + * etiketi gizlenir, OpenRGB'nin kullandığı ayarlar yerinde kalır. * * Her denetimin yanında hangi çıkışlarda geçerli olduğu yazar. Etiketler * kod yollarına göre: shared/lighting-render.js → WDL + OpenRGB; lighting.brightness @@ -16,14 +18,29 @@ const WDL = 'Windows Dynamic Lighting'; const ORGB = 'OpenRGB'; + const STATIC_MODES = ['single-color', 'per-device', 'per-led']; + + function wdlOn() { + return !!(typeof window !== 'undefined' && window.SV_PLATFORM && window.SV_PLATFORM.isWindows); + } + + function shownTags(names) { + if (wdlOn()) return names; + return names.filter((n) => n !== WDL); + } + function backends(names) { const el = P().el; return el('div', { class: 'lighting-backends' }, names.map((n) => el('span', { class: 'lighting-backend-tag', text: n }))); } - function wrap(ctrl, names) { + /* keep: etiket kalmayınca denetimi yine çiz (mod listesi). WDL-only + kaydırıcılar keep olmadan tamamen düşer. */ + function wrap(ctrl, names, keep) { const el = P().el; - return el('div', { class: 'lighting-tagged' }, [ctrl, backends(names)]); + const tags = shownTags(names); + if (!tags.length) return keep ? ctrl : null; + return el('div', { class: 'lighting-tagged' }, [ctrl, backends(tags)]); } /* Tek ayar sıfırlama: admin appendGrouped ile aynı data-path + ctrl-reset. @@ -61,7 +78,9 @@ const nodes = []; nodes.push(el('div', { class: 'lighting-general-banner', - text: 'Bu ayarlar Windows Dynamic Lighting ve OpenRGB çıkışlarının ortak görünümüdür. Art-Net kendi kartındaki ayarları kullanır. Bir çıkışı açmadan da burada düzenleyebilirsiniz.', + text: wdlOn() + ? 'Bu ayarlar Windows Dynamic Lighting ve OpenRGB çıkışlarının ortak görünümüdür. Art-Net kendi kartındaki ayarları kullanır. Bir çıkışı açmadan da burada düzenleyebilirsiniz.' + : 'Bu ayarlar OpenRGB çıkışının görünümüdür. Art-Net kendi kartındaki ayarları kullanır. Bir çıkışı açmadan da burada düzenleyebilirsiniz.', })); const themedDropdown = (label, value, options, onChange, description) => { @@ -124,8 +143,9 @@ apply(true); }, false), key); - const modes = (P().lightingModes && P().lightingModes()) || []; - const staticMode = ['single-color', 'per-device', 'per-led'].includes(lighting.mode); + let modes = (P().lightingModes && P().lightingModes()) || []; + if (!wdlOn()) modes = modes.filter((m) => STATIC_MODES.indexOf(String(m.value)) < 0); + const staticMode = STATIC_MODES.includes(lighting.mode); const dynamicMode = !staticMode; nodes.push(wrap( @@ -133,20 +153,28 @@ lighting.mode = value; apply(true); }, true), 'mode'), - staticMode ? [WDL] : [WDL, ORGB] + staticMode ? [WDL] : [WDL, ORGB], + !wdlOn() )); - if (staticMode) { + if (staticMode && wdlOn()) { nodes.push(el('div', { class: 'lighting-mode-help', text: 'Statik modlar (tek renk, aygıt başına, LED başına) yalnız Windows Dynamic Lighting ile çalışır. OpenRGB sesi izleyen dinamik modları sürer. Aygıt/LED renk boyası Windows Dynamic Lighting kartındadır.', })); + } else if (staticMode) { + nodes.push(el('div', { + class: 'lighting-mode-help', + text: 'Kayıtlı aydınlatma modu bu sistemde yok. Listeden OpenRGB\'nin sürebildiği bir mod seçin.', + })); } - nodes.push(wrap(rangeRow('brightness', 'Genel Parlaklık', 0, 1, 0.01, true), [WDL])); - nodes.push(el('div', { - class: 'lighting-mode-help', - text: 'OpenRGB parlaklığı OpenRGB kartındaki Parlaklık kaydırıcısındadır; buradaki değer Windows Dynamic Lighting içindir.', - })); + if (wdlOn()) { + nodes.push(wrap(rangeRow('brightness', 'Genel Parlaklık', 0, 1, 0.01, true), [WDL])); + nodes.push(el('div', { + class: 'lighting-mode-help', + text: 'OpenRGB parlaklığı OpenRGB kartındaki Parlaklık kaydırıcısındadır; buradaki değer Windows Dynamic Lighting içindir.', + })); + } if (lighting.mode === 'single-color') { nodes.push(wrap(colorRow('color', 'Tek Renk'), [WDL])); @@ -165,11 +193,13 @@ nodes.push(wrap(rangeRow('baseLevel', 'Sessizlikte Işık', 0.02, 0.6, 0.01, true), [WDL, ORGB])); nodes.push(wrap(rangeRow('spread', 'Renk Yayılımı', 0.1, 4, 0.05), [WDL, ORGB])); } - nodes.push(wrap(rangeRow('updateRate', 'Güncelleme Hızı', 5, 60, 1), [WDL])); - nodes.push(el('div', { - class: 'lighting-mode-help', - text: 'OpenRGB kare hızı OpenRGB kartındaki Güncelleme Hızı kaydırıcısındadır.', - })); + if (wdlOn()) { + nodes.push(wrap(rangeRow('updateRate', 'Güncelleme Hızı', 5, 60, 1), [WDL])); + nodes.push(el('div', { + class: 'lighting-mode-help', + text: 'OpenRGB kare hızı OpenRGB kartındaki Güncelleme Hızı kaydırıcısındadır.', + })); + } nodes.push(wrap(rangeRow('saturation', 'Renk Doygunluğu', 0, 1.5, 0.01), [WDL, ORGB])); } diff --git a/src/admin/nowplaying-panel.js b/src/admin/nowplaying-panel.js index 81909a05..4ae82568 100644 --- a/src/admin/nowplaying-panel.js +++ b/src/admin/nowplaying-panel.js @@ -46,9 +46,12 @@ if (!statusEl || !statusEl.isConnected) return; const N = NP(); if (status && status.supported === false) { - statusEl.textContent = status.platform === 'win32' - ? 'Sistemden okunamıyor' + (status.error ? ' — ' + status.error : '') - : 'Sistemden okuma yalnızca Windows’ta çalışıyor'; + statusEl.textContent = 'Sistemden okunamıyor' + (status.error ? ' — ' + status.error : ''); + statusEl.className = 'txt-info np-status bad'; + return; + } + if ((!live || !live.has) && status && status.error) { + statusEl.textContent = 'Sistemden okunamıyor — ' + status.error; statusEl.className = 'txt-info np-status bad'; return; } @@ -101,9 +104,7 @@ const nodes = []; const isWin = !!(window.SV_PLATFORM && window.SV_PLATFORM.isWindows); - if (!isWin && (C.source || 'system') === 'system') { - C.source = 'manual'; - } + if (!isWin && (C.source || 'system') === 'system') C.source = 'manual'; const src = C.source || (isWin ? 'system' : 'manual'); if (src === 'system' && isWin && window.api && window.api.nowPlayingSubscribe) { window.api.nowPlayingSubscribe(true); @@ -115,13 +116,8 @@ const sourceOpts = isWin ? SOURCE_LABELS : [['manual', 'Elle Yaz']]; nodes.push(SP().miniSelect('Kaynak', sourceOpts, () => src, (v) => { - if (!isWin && v === 'system') { C.source = 'manual'; } - else C.source = v; + C.source = isWin ? v : 'manual'; }, rerender)); - if (!isWin) { - nodes.push(el('div', { class: 'studio-note dim-hint', - text: 'Sistemden okuma (SMTC) yalnızca Windows’ta çalışır. Bu platformda parçayı elle yazın.' })); - } if (src === 'system') { statusEl = el('span', { class: 'txt-info np-status', text: 'okunuyor…' }); diff --git a/src/admin/scene-panels.js b/src/admin/scene-panels.js index b8fd3cb4..4a43d58d 100644 --- a/src/admin/scene-panels.js +++ b/src/admin/scene-panels.js @@ -759,9 +759,7 @@ } } else if (src === 'now') { const isWin = isWindowsPlatform(); - if (!isWin && txt.nowSource === 'system') { - txt.nowSource = 'manual'; - } + if (!isWin && (txt.nowSource || 'system') === 'system') txt.nowSource = 'manual'; const isAuto = isWin && (txt.nowSource || 'system') === 'system'; out.push(miniSelect('Gösterilen Alan', [ @@ -770,18 +768,17 @@ ['both', 'Parça ve Sanatçı'], ], () => txt.field || 'both', (v) => { txt.field = v; }, rerender)); - if (isWin) { - if (isAuto && window.api && window.api.nowPlayingSubscribe) { + if (isWin && isAuto && window.api && window.api.nowPlayingSubscribe) { + window.api.nowPlayingSubscribe(true); + } + if (isWin) out.push(miniToggle('Sistemden Otomatik Doldur', () => isAuto, (v) => { + txt.nowSource = v ? 'system' : 'manual'; + if (v && window.api && window.api.nowPlayingSubscribe) { window.api.nowPlayingSubscribe(true); } - out.push(miniToggle('Sistemden Otomatik Doldur', () => isAuto, (v) => { - txt.nowSource = v ? 'system' : 'manual'; - if (v && window.api && window.api.nowPlayingSubscribe) { - window.api.nowPlayingSubscribe(true); - } - }, rerender)); + }, rerender)); - out.push(miniToggle('Şarkı Resmini Göster', () => txt.showArtwork !== false, (v) => { + if (isWin) out.push(miniToggle('Şarkı Resmini Göster', () => txt.showArtwork !== false, (v) => { txt.showArtwork = v; P().push(true); }, rerender)); @@ -845,18 +842,6 @@ ])); } } - } else { - // macOS / Linux - out.push(miniToggle('Sistemden Otomatik Doldur', () => false, () => {}, null, { - disabled: true, - badge: 'Yalnızca Windows', - title: 'Bu özellik şu anda yalnızca Windows (SMTC) üzerinde desteklenmektedir.', - })); - out.push(el('div', { - class: 'studio-note dim-hint', - text: 'Sistem medya oturumunu (SMTC) otomatik okuma şu anda yalnızca Windows’ta desteklenmektedir. Başlık ve sanatçı bilgilerini aşağıdan elle girebilirsiniz.', - })); - } const titleLabel = isAuto ? 'Yedek Parça Adı' : 'Parça Adı'; const artistLabel = isAuto ? 'Yedek Sanatçı' : 'Sanatçı'; diff --git a/src/admin/studio.css b/src/admin/studio.css index 5217ca1c..44492b5c 100644 --- a/src/admin/studio.css +++ b/src/admin/studio.css @@ -184,10 +184,9 @@ input.p-in[type='color'] { padding: 2px; height: 28px; width: 46px; flex: none; .client-addr { color: var(--muted2); font: 10.5px 'Cascadia Code', 'Consolas', monospace; margin-left: auto; } /* ========================= SAHNE ÜRETİCİ ========================= */ -/* Button row (#579 / layout): flex:1 (basis 0%) forced equal columns in a - ~300px card, so long TR/EN labels ("Kütüphaneye Kaydet" / "Save to Library") - overflowed and sat flush on the right edge. Wrap with a content-ish basis - and even gap; don't shrink below the label. Same idea as #622 add-row. */ +/* Button row (#579): basis 0% squeezed long labels onto the right edge. + Size from the label, wrap onto the next line, and let a long English + label break inside the button instead of spilling past the card. */ .gen-actions { display: flex; flex-wrap: wrap; @@ -196,9 +195,13 @@ input.p-in[type='color'] { padding: 2px; height: 28px; width: 46px; flex: none; align-items: stretch; } .gen-actions .btn { - flex: 1 1 8.5rem; - min-width: min(100%, 8.5rem); + flex: 1 1 auto; + min-width: 0; max-width: 100%; + white-space: normal; + overflow-wrap: anywhere; + text-align: center; + line-height: 1.25; } /* Mix history: icon-only chevrons stay compact; Save shares the rest without margin-left:auto so disabled state does not jump to the far edge. */ diff --git a/src/admin/text-panel.js b/src/admin/text-panel.js index e95f1c09..b0073598 100644 --- a/src/admin/text-panel.js +++ b/src/admin/text-panel.js @@ -59,17 +59,6 @@ } } - function isWindowsPlatform() { - if (SP() && typeof SP().isWindows === 'function') return SP().isWindows(); - if (typeof window !== 'undefined' && window.SV_PLATFORM && typeof window.SV_PLATFORM.isWindows === 'boolean') { - return window.SV_PLATFORM.isWindows; - } - if (typeof process !== 'undefined' && process.platform) { - return process.platform === 'win32'; - } - return false; - } - function panel() { const el = P().el; const cfg = P().cfg(); @@ -107,18 +96,17 @@ ['artist', 'Sanatçı Adı'], ['both', 'Parça ve Sanatçı'], ], () => T.field || 'both', (v) => { T.field = v; sync(); }, rerender)); - const isWin = isWindowsPlatform(); - if (!isWin && T.nowSource === 'system') { + const isWin = !!(window.SV_PLATFORM && window.SV_PLATFORM.isWindows); + if (!isWin && (T.nowSource || 'system') === 'system') { T.nowSource = 'manual'; sync(); } const isAuto = isWin && (T.nowSource || 'system') === 'system'; - if (isWin) { - if (isAuto && window.api && window.api.nowPlayingSubscribe) { - window.api.nowPlayingSubscribe(true); - } - nodes.push(SP().miniToggle('Sistemden Otomatik Doldur', () => isAuto, (v) => { + if (isWin && isAuto && window.api && window.api.nowPlayingSubscribe) { + window.api.nowPlayingSubscribe(true); + } + if (isWin) nodes.push(SP().miniToggle('Sistemden Otomatik Doldur', () => isAuto, (v) => { T.nowSource = v ? 'system' : 'manual'; sync(); if (v && window.api && window.api.nowPlayingSubscribe) { @@ -127,7 +115,7 @@ P().push(true); }, rerender)); - nodes.push(SP().miniToggle('Şarkı Resmini Göster', () => T.showArtwork !== false, (v) => { + if (isWin) nodes.push(SP().miniToggle('Şarkı Resmini Göster', () => T.showArtwork !== false, (v) => { T.showArtwork = v; sync(); P().push(true); @@ -236,38 +224,6 @@ }, }))); } - } else { - // macOS / Linux - nodes.push(SP().miniToggle('Sistemden Otomatik Doldur', () => false, () => {}, null, { - disabled: true, - badge: 'Yalnızca Windows', - title: 'Bu özellik şu anda yalnızca Windows (SMTC) üzerinde desteklenmektedir.', - })); - nodes.push(el('div', { - class: 'studio-note dim-hint', - text: 'Sistem medya oturumunu (SMTC) otomatik okuma şu anda yalnızca Windows’ta desteklenmektedir. Başlık ve sanatçı bilgilerini aşağıdan elle girebilirsiniz.', - })); - nodes.push(P().row('Başlık', el('input', { - class: 'p-in', type: 'text', placeholder: 'Örn: Şarkı Başlığı', - value: (T.nowPlaying && T.nowPlaying.title) || '', - oninput: (e) => { - T.nowPlaying = T.nowPlaying || {}; - T.nowPlaying.title = e.target.value; - sync(); - P().push(false); - }, - }))); - nodes.push(P().row('Sanatçı', el('input', { - class: 'p-in', type: 'text', placeholder: 'Örn: Sanatçı Adı', - value: (T.nowPlaying && T.nowPlaying.artist) || '', - oninput: (e) => { - T.nowPlaying = T.nowPlaying || {}; - T.nowPlaying.artist = e.target.value; - sync(); - P().push(false); - }, - }))); - } } else { // ------------------------------------------------------------ söz diff --git a/src/shared/i18n.js b/src/shared/i18n.js index ae6d3a2b..e4f36859 100644 --- a/src/shared/i18n.js +++ b/src/shared/i18n.js @@ -2011,6 +2011,9 @@ 'Genel Işık Ayarları': 'General Light Settings', 'Mod, renk ve ses tepkisi — Windows Dynamic Lighting ve OpenRGB ortak görünümü. Her ayarın hangi çıkışlarda geçerli olduğu yanında yazar. Art-Net kendi kartındaki ayarları kullanır.': 'Mode, colour and audio response — the shared look for Windows Dynamic Lighting and OpenRGB. Each control names the outputs it applies to. Art-Net uses the settings on its own card.', 'Bu ayarlar Windows Dynamic Lighting ve OpenRGB çıkışlarının ortak görünümüdür. Art-Net kendi kartındaki ayarları kullanır. Bir çıkışı açmadan da burada düzenleyebilirsiniz.': 'These settings are the shared look for Windows Dynamic Lighting and OpenRGB. Art-Net uses the settings on its own card. You can edit them here even before turning an output on.', + 'Mod, renk ve ses tepkisi — OpenRGB çıkışı. Art-Net kendi kartındaki ayarları kullanır.': 'Mode, colour and audio response — the OpenRGB output. Art-Net uses the settings on its own card.', + 'Bu ayarlar OpenRGB çıkışının görünümüdür. Art-Net kendi kartındaki ayarları kullanır. Bir çıkışı açmadan da burada düzenleyebilirsiniz.': 'These settings are the look of the OpenRGB output. Art-Net uses the settings on its own card. You can edit them here even before turning an output on.', + 'Kayıtlı aydınlatma modu bu sistemde yok. Listeden OpenRGB\'nin sürebildiği bir mod seçin.': 'The saved lighting mode is not available on this system. Pick a mode from the list that OpenRGB can drive.', 'Ortak görünüm ayarları (mod, parlaklık, ses tepkisi, renkler) aşağıda Genel Işık Ayarları kartındadır. Bu kartta yalnız Windows aygıtlarına özel renk boyama vardır.': 'Shared look settings (mode, brightness, audio response, colours) are in the General Light Settings card below. This card only has colour painting that is specific to Windows devices.', 'Statik modlar (tek renk, aygıt başına, LED başına) yalnız Windows Dynamic Lighting ile çalışır. OpenRGB sesi izleyen dinamik modları sürer. Aygıt/LED renk boyası Windows Dynamic Lighting kartındadır.': 'Static modes (single colour, per device, per LED) work only with Windows Dynamic Lighting. OpenRGB drives the dynamic modes that follow the audio. Device/LED colour painting is on the Windows Dynamic Lighting card.', 'OpenRGB parlaklığı OpenRGB kartındaki Parlaklık kaydırıcısındadır; buradaki değer Windows Dynamic Lighting içindir.': 'OpenRGB brightness is the Brightness slider on the OpenRGB card; the value here is for Windows Dynamic Lighting.', @@ -2054,7 +2057,7 @@ 'Kütüphaneden Karışım': 'Mash-up from Your Library', 'Yeni Karışım': 'New Mash-up', 'Her parçayı listede görünen presetlerden rastgele çeker': 'Draws every part at random from the presets shown in the list', - 'Ekrandakinden Başla': 'Start from the Preset on Screen', + 'Ekrandakinden Başla': 'Use On-Screen Preset', 'Altı parçanın hepsini ekrandaki presetten alır; sonra tek tek değiştirin': 'Takes all six parts from the preset on screen; then change them one by one', 'Bu parçayı yeniden çek': 'Draw this part again', 'Önceki karışım': 'Previous mash-up', diff --git a/tests/admin-masonry.test.js b/tests/admin-masonry.test.js new file mode 100644 index 00000000..71408b43 --- /dev/null +++ b/tests/admin-masonry.test.js @@ -0,0 +1,54 @@ +'use strict'; +/* Kart ızgarası yeniden çizimde doğal boyu ölçmeli. Masonry sınıfı açıkken + ölçmek Linux'ta kartları 4 px sanıp üst üste bindiriyordu. */ +const test = require('node:test'); +const assert = require('node:assert'); +const fs = require('fs'); +const path = require('path'); + +const admin = fs.readFileSync(path.join(__dirname, '..', 'src', 'admin', 'admin.js'), 'utf8'); +const css = fs.readFileSync(path.join(__dirname, '..', 'src', 'admin', 'admin.css'), 'utf8'); + +test('yeniden çizim masonry sınıfını kartlar doğmadan kapatır', () => { + const i = admin.indexOf('function render()'); + assert.ok(i > 0); + const body = admin.slice(i, i + 700); + const off = body.indexOf("classList.remove('masonry')"); + const clear = body.indexOf('root.innerHTML'); + assert.ok(off > 0 && clear > off, 'masonry, kartlar silinmeden kapanmalı'); +}); + +test('layoutCards boyu 4 px ızgara kapalıyken ölçer', () => { + const i = admin.indexOf('function layoutCards'); + const end = admin.indexOf('function buildCard', i); + assert.ok(i > 0 && end > i); + const body = admin.slice(i, end); + const off = body.indexOf("root.classList.remove('masonry')"); + const measure = body.indexOf('const heights = kids.map'); + const on = body.indexOf("root.classList.add('masonry')"); + assert.ok(off > 0 && measure > off && on > measure, 'ölçüm masonry açılmadan önce olmalı'); + assert.match(body.slice(measure, on), /getBoundingClientRect\(\)\.height/); + /* Gözlemci span'i küçültmesin: 4 px'lik satır okuması kartları yeniden bindirir. */ + assert.match(body, /if \(next > cur\) c\.style\.gridRowEnd = s/); +}); + +test('kart içeriği satır ızgarasına göre büzülmez', () => { + assert.match(css, /\.card\s*\{[^}]*height:\s*max-content/s); +}); + +test('üretici çifti geniş pencerede satırı paylaşır', () => { + const fn = admin.indexOf('function placeGeneratorPair'); + assert.ok(fn > 0); + const body = admin.slice(fn, fn + 700); + assert.match(body, /data-card="scenegen"/); + assert.match(body, /data-card="mdgen"/); + assert.match(body, /Math\.ceil\(cols \/ 2\)/); + assert.match(body, /\/ -1/); + const layout = admin.slice(admin.indexOf('function layoutCards'), admin.indexOf('function buildCard')); + const place = layout.indexOf('placeGeneratorPair(root)'); + const measure = layout.indexOf('const heights = kids.map'); + assert.ok(place > 0 && place < measure, 'pay, boy ölçülmeden önce verilmeli'); + assert.match(layout, /clientWidth/); + assert.match(css, /\.sections\s*\{[^}]*overflow-x:\s*hidden/s); + assert.match(css, /\.card\s*\{[^}]*min-width:\s*0/s); +}); diff --git a/tests/milkdrop-gen-panel.test.js b/tests/milkdrop-gen-panel.test.js index 1c845c02..d473cd40 100644 --- a/tests/milkdrop-gen-panel.test.js +++ b/tests/milkdrop-gen-panel.test.js @@ -263,7 +263,9 @@ test('üretici düğme satırı kaydırılabilir ve sağa yapışmaz', () => { geçmişindeki oklar mdmix-nav ile kompakt. */ const css = fs.readFileSync(path.join(__dirname, '..', 'src', 'admin', 'studio.css'), 'utf-8'); assert.match(css, /\.gen-actions\s*\{[^}]*flex-wrap:\s*wrap/s); - assert.match(css, /\.gen-actions\s+\.btn\s*\{[^}]*flex:\s*1\s+1\s+8\.5rem/s); + assert.match(css, /\.gen-actions\s+\.btn\s*\{[^}]*min-width:\s*0/s); + assert.match(css, /\.gen-actions\s+\.btn\s*\{[^}]*white-space:\s*normal/s); + assert.match(css, /\.gen-actions\s+\.btn\s*\{[^}]*overflow-wrap:\s*anywhere/s); assert.doesNotMatch(css, /\.gen-actions\s+\.btn\s*\{\s*flex:\s*1;\s*\}/); assert.match(css, /\.gen-actions\.mdmix-nav/); const gen = fs.readFileSync(path.join(__dirname, '..', 'src', 'admin', 'milkdrop-gen.js'), 'utf-8'); diff --git a/tests/nowplaying-cover-platform.test.js b/tests/nowplaying-cover-platform.test.js index 810503ed..b21de569 100644 --- a/tests/nowplaying-cover-platform.test.js +++ b/tests/nowplaying-cover-platform.test.js @@ -39,6 +39,7 @@ test('kaynak: dynamicTheme karti Windows-only', () => { test('kaynak: NP sistem kaynagi non-Windows manuel', () => { assert.match(panelSrc, /isWin/); assert.match(panelSrc, /C\.source\s*=\s*'manual'/); + assert.doesNotMatch(panelSrc, /Sistemden okuma \(SMTC\)/); }); test('UI: NP panel cover toggle is always visible (not only inside foldable)', () => { const toggleAt = panelSrc.indexOf("miniToggle('Kapağı Göster'");