Use Glass Stage icons for playlist and offline controls
This commit is contained in:
@@ -2,6 +2,8 @@
|
|||||||
(function(root) {
|
(function(root) {
|
||||||
'use strict';
|
'use strict';
|
||||||
const paths = {
|
const paths = {
|
||||||
|
trash:'<path d="M3 6h18M9 6V3h6v3M5 6l1 15h12l1-15M10 10v7M14 10v7"/>',
|
||||||
|
music:'<path d="M9 18V5l11-2v13M9 8l11-2"/><ellipse cx="6" cy="18" rx="3" ry="2"/><ellipse cx="17" cy="16" rx="3" ry="2"/>',
|
||||||
add:'<path d="M12 5v14M5 12h14"/>', close:'<path d="m6 6 12 12M18 6 6 18"/>',
|
add:'<path d="M12 5v14M5 12h14"/>', close:'<path d="m6 6 12 12M18 6 6 18"/>',
|
||||||
search:'<circle cx="10.5" cy="10.5" r="6.5"/><path d="m16 16 5 5"/>',
|
search:'<circle cx="10.5" cy="10.5" r="6.5"/><path d="m16 16 5 5"/>',
|
||||||
save:'<path d="M12 3v12m-5-5 5 5 5-5M4 17v4h16v-4"/>',
|
save:'<path d="M12 3v12m-5-5 5 5 5-5M4 17v4h16v-4"/>',
|
||||||
@@ -20,7 +22,7 @@
|
|||||||
down:'<path d="m6 9 6 6 6-6"/>', right:'<path d="m9 6 6 6-6 6"/>', back:'<path d="M20 12H4m6-6-6 6 6 6"/>', more:'<circle cx="5" cy="12" r="1"/><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/>',
|
down:'<path d="m6 9 6 6 6-6"/>', right:'<path d="m9 6 6 6-6 6"/>', back:'<path d="M20 12H4m6-6-6 6 6 6"/>', more:'<circle cx="5" cy="12" r="1"/><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/>',
|
||||||
devices:'<rect x="3" y="4" width="14" height="12" rx="2"/><path d="M7 20h6M10 16v4"/><rect x="17" y="10" width="5" height="11" rx="1"/>'
|
devices:'<rect x="3" y="4" width="14" height="12" rx="2"/><path d="M7 20h6M10 16v4"/><rect x="17" y="10" width="5" height="11" rx="1"/>'
|
||||||
};
|
};
|
||||||
const glyphs = {'+':'add','✕':'close','×':'close','✖':'close','🔍':'search','🔎':'search','⬇':'save','⤓':'save','💾':'save','▶':'play','⏸':'pause','⏭':'next','⏮':'previous','↻':'repeat','↺':'repeat','✎':'edit','✏':'edit','📝':'edit','🎤':'lyrics','↗':'share','⧉':'copy','✓':'check','📁':'folder','⚙':'settings','📋':'chapters','🕘':'clock','🔀':'shuffle','📡':'devices','▾':'down','▸':'right','←':'back','⋯':'more'};
|
const glyphs = {'+':'add','✕':'close','×':'close','✖':'close','🔍':'search','🔎':'search','⬇':'save','⤓':'save','💾':'save','▶':'play','⏸':'pause','⏭':'next','⏮':'previous','↻':'repeat','↺':'repeat','✎':'edit','✏':'edit','📝':'edit','🎤':'lyrics','↗':'share','⧉':'copy','✓':'check','📁':'folder','⚙':'settings','📋':'chapters','🕘':'clock','🔀':'shuffle','📡':'devices','▾':'down','▸':'right','←':'back','⋯':'more','⋮':'more','🗑':'trash','🎵':'music'};
|
||||||
function leadingGlyph(text) {
|
function leadingGlyph(text) {
|
||||||
const prefix = text.match(/^\s*/)[0], rest = text.slice(prefix.length);
|
const prefix = text.match(/^\s*/)[0], rest = text.slice(prefix.length);
|
||||||
const glyph = Object.keys(glyphs).find(key => rest.startsWith(key));
|
const glyph = Object.keys(glyphs).find(key => rest.startsWith(key));
|
||||||
@@ -30,7 +32,7 @@
|
|||||||
}
|
}
|
||||||
function mount(doc) {
|
function mount(doc) {
|
||||||
const decorations = new Set(); let scheduled=false;
|
const decorations = new Set(); let scheduled=false;
|
||||||
const selector = '#listPane button, .sidebar button, .smart-item .pl-name, .notes-tab, .notes-fold, .mini-btn';
|
const selector = '#listPane button, .sidebar button, .smart-item .pl-name, .notes-tab, .notes-fold, .mini-btn, .sidebar .playlist-item:not(.smart-item), .pl-offline-badge.all, .home-pl-offline.all, .home-pl-play, .home-pl-thumb-fallback';
|
||||||
const svg = kind => { const icon=doc.createElementNS('http://www.w3.org/2000/svg','svg'); icon.setAttribute('viewBox','0 0 24 24');icon.setAttribute('aria-hidden','true');icon.classList.add('glass-control-icon');icon.innerHTML=paths[kind];return icon; };
|
const svg = kind => { const icon=doc.createElementNS('http://www.w3.org/2000/svg','svg'); icon.setAttribute('viewBox','0 0 24 24');icon.setAttribute('aria-hidden','true');icon.classList.add('glass-control-icon');icon.innerHTML=paths[kind];return icon; };
|
||||||
const observer=new root.MutationObserver(schedule), themeObserver=new root.MutationObserver(schedule);
|
const observer=new root.MutationObserver(schedule), themeObserver=new root.MutationObserver(schedule);
|
||||||
function restore(record) {
|
function restore(record) {
|
||||||
@@ -46,12 +48,18 @@
|
|||||||
if(parent.querySelector('svg:not(.glass-control-icon),.legacy-nav-icon'))continue;
|
if(parent.querySelector('svg:not(.glass-control-icon),.legacy-nav-icon'))continue;
|
||||||
for(const node of [...parent.childNodes]) {
|
for(const node of [...parent.childNodes]) {
|
||||||
if(node.nodeType!==3)continue;const match=leadingGlyph(node.textContent);if(!match)continue;
|
if(node.nodeType!==3)continue;const match=leadingGlyph(node.textContent);if(!match)continue;
|
||||||
const icon=svg(match.kind), original=doc.createElement('span'); original.className='glass-original-glyph';original.textContent=match.original;
|
const icon=svg(match.kind);
|
||||||
|
if(parent.matches('.pl-offline-badge,.home-pl-offline')) { icon.removeAttribute('aria-hidden'); icon.setAttribute('role','img'); icon.setAttribute('aria-label',parent.title || 'Available offline'); }
|
||||||
|
const original=doc.createElement('span'); original.className='glass-original-glyph';original.textContent=match.original;
|
||||||
node.textContent=match.rest;parent.insertBefore(icon,node);parent.insertBefore(original,node);
|
node.textContent=match.rest;parent.insertBefore(icon,node);parent.insertBefore(original,node);
|
||||||
const addedLabel=parent.tagName==='BUTTON'&&!parent.hasAttribute('aria-label')&&!parent.textContent.replace(match.original,'').trim();
|
const addedLabel=parent.tagName==='BUTTON'&&!parent.hasAttribute('aria-label')&&!parent.textContent.replace(match.original,'').trim();
|
||||||
if(addedLabel)parent.setAttribute('aria-label',parent.title || ({play:'Play',pause:'Pause',close:'Close',add:'Add',save:'Save'}[match.kind] || match.kind));
|
if(addedLabel)parent.setAttribute('aria-label',parent.title || ({play:'Play',pause:'Pause',close:'Close',add:'Add',save:'Save'}[match.kind] || match.kind));
|
||||||
decorations.add({parent,svg:icon,original,addedLabel});break;
|
decorations.add({parent,svg:icon,original,addedLabel});break;
|
||||||
}
|
}
|
||||||
|
if(parent.matches('.sidebar .playlist-item:not(.smart-item)')&&!parent.querySelector('svg')) {
|
||||||
|
const icon=svg('folder'); parent.prepend(icon);
|
||||||
|
decorations.add({parent,svg:icon,original:doc.createElement('span'),addedLabel:false});
|
||||||
|
}
|
||||||
if(parent.matches('.notes-tab')&&!parent.querySelector('svg')&&paths[parent.dataset.tab==='markers'?'chapters':parent.dataset.tab]) {
|
if(parent.matches('.notes-tab')&&!parent.querySelector('svg')&&paths[parent.dataset.tab==='markers'?'chapters':parent.dataset.tab]) {
|
||||||
const kind=parent.dataset.tab==='markers'?'chapters':parent.dataset.tab,icon=svg(kind);parent.prepend(icon);
|
const kind=parent.dataset.tab==='markers'?'chapters':parent.dataset.tab,icon=svg(kind);parent.prepend(icon);
|
||||||
const original=doc.createElement('span');decorations.add({parent,svg:icon,original,addedLabel:false});
|
const original=doc.createElement('span');decorations.add({parent,svg:icon,original,addedLabel:false});
|
||||||
|
|||||||
@@ -7,3 +7,9 @@ test('glass icon substitution preserves leading whitespace and remaining labels'
|
|||||||
assert.equal(leadingGlyph('Clear all'),null);
|
assert.equal(leadingGlyph('Clear all'),null);
|
||||||
assert.equal(leadingGlyph('⏸').kind,'pause');
|
assert.equal(leadingGlyph('⏸').kind,'pause');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('playlist trash, menu and music glyphs use drawn glass icons',()=>{
|
||||||
|
assert.equal(leadingGlyph('🗑️ Delete').kind,'trash');
|
||||||
|
assert.equal(leadingGlyph('⋮').kind,'more');
|
||||||
|
assert.equal(leadingGlyph('🎵').kind,'music');
|
||||||
|
});
|
||||||
|
|||||||
@@ -52,3 +52,9 @@ html[data-layout="glass-stage"] .sec-rail-btn { width:44px; height:44px; border-
|
|||||||
html[data-layout="glass-stage"] .sec-rail-btn.active { background:var(--tile-hi); color:var(--accent-bright); border-color:var(--glass-line); }
|
html[data-layout="glass-stage"] .sec-rail-btn.active { background:var(--tile-hi); color:var(--accent-bright); border-color:var(--glass-line); }
|
||||||
html[data-layout="glass-stage"] .sec-rail-label { right:auto; left:0; bottom:50px; }
|
html[data-layout="glass-stage"] .sec-rail-label { right:auto; left:0; bottom:50px; }
|
||||||
html[data-layout="glass-stage"] #listPane { padding-bottom:calc(var(--bottom-chrome,0px) + 88px) !important; }
|
html[data-layout="glass-stage"] #listPane { padding-bottom:calc(var(--bottom-chrome,0px) + 88px) !important; }
|
||||||
|
|
||||||
|
/* Playlist rows and offline state use the same drawn icons as Now Playing. */
|
||||||
|
html[data-layout="glass-stage"] .sidebar .playlist-item::before { content:none; }
|
||||||
|
html[data-layout="glass-stage"] :is(.pl-offline-badge.all,.home-pl-offline.all) { display:inline-grid; place-items:center; flex:none; width:24px; height:24px; padding:0; border-radius:8px; background:var(--tile-hi); border:1px solid var(--glass-line); color:var(--accent-bright); box-shadow:none; }
|
||||||
|
html[data-layout="glass-stage"] :is(.pl-offline-badge.partial,.home-pl-offline.partial) { font-size:11px; }
|
||||||
|
html[data-layout="glass-stage"] .home-pl-play { display:grid; place-items:center; width:44px; height:44px; border-radius:18px; background:var(--tile); border:1px solid var(--glass-line); color:var(--text); }
|
||||||
|
|||||||
@@ -44,3 +44,40 @@ for (const layout of ['glass-stage', 'classic']) for (const width of [390, 1440]
|
|||||||
await before.close();
|
await before.close();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const beforeIcons = execFileSync('git', ['show', '79a3c28:frontend/glass-controls.js'], { encoding: 'utf8' });
|
||||||
|
async function sidebarFixture(page, layout) {
|
||||||
|
await openClassic(page, { populated: true, settings: { autoPreload: false } });
|
||||||
|
await page.addStyleTag({ content: '*,*::before,*::after{animation:none!important;transition:none!important;caret-color:transparent!important}' });
|
||||||
|
await page.evaluate(layout => {
|
||||||
|
data.settings.layout = layout; applyAppearance();
|
||||||
|
data.playlists[0].videos.forEach(v => cachedIds.add(v.id));
|
||||||
|
view = { type: 'playlist', id: 'studio' }; render(); openSidebar();
|
||||||
|
}, layout);
|
||||||
|
await page.evaluate(async () => { await document.fonts.ready; await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve))); });
|
||||||
|
}
|
||||||
|
for (const layout of ['glass-stage', 'classic']) for (const width of [390, 1440]) {
|
||||||
|
test(`${layout} playlist sidebar icons at ${width}`, async ({ page, context }) => {
|
||||||
|
await page.setViewportSize({ width, height: 900 });
|
||||||
|
const before = await context.newPage(); await before.setViewportSize({ width, height: 900 });
|
||||||
|
await before.route('**/glass-controls.js', route => route.fulfill({ contentType: 'text/javascript', body: beforeIcons }));
|
||||||
|
await sidebarFixture(before, layout);
|
||||||
|
const baseline = await before.screenshot({ path: `/tmp/ytplayer-done8-B-before-${layout}-${width}.png` });
|
||||||
|
await sidebarFixture(page, layout);
|
||||||
|
const result = await page.screenshot({ path: `/tmp/ytplayer-done8-B-after-${layout}-${width}.png` });
|
||||||
|
const badge = page.locator('.sidebar .pl-offline-badge.all').first();
|
||||||
|
if (layout === 'classic') {
|
||||||
|
expect(differentPixels(baseline, result)).toBeLessThanOrEqual(32);
|
||||||
|
await expect(badge).toHaveText('⬇');
|
||||||
|
} else {
|
||||||
|
await expect(badge.locator('svg')).toHaveCount(1);
|
||||||
|
expect(await badge.locator('svg').evaluate(el => getComputedStyle(el).strokeWidth)).toBe('1.9px');
|
||||||
|
await expect(page.locator('.home-pl-play svg').first()).toHaveCount(1);
|
||||||
|
await expect(page.locator('#listActions .la-primary svg')).toHaveCount(1);
|
||||||
|
await page.evaluate(() => { data.settings.layout = 'classic'; applyAppearance(); });
|
||||||
|
await expect(badge).toHaveText('⬇');
|
||||||
|
await expect(page.locator('.glass-control-icon')).toHaveCount(0);
|
||||||
|
}
|
||||||
|
await before.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user