Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
305 lines
7.1 KiB
JavaScript
Executable file
305 lines
7.1 KiB
JavaScript
Executable file
(function(window) {
|
|
'use strict';
|
|
|
|
var proto = Object.create(HTMLElement.prototype);
|
|
|
|
|
|
proto.createdCallback = function() {
|
|
var style = document.createElement('style');
|
|
style.innerHTML =
|
|
`music-view-stack {
|
|
position: relative;
|
|
width: 100%;
|
|
height: 100%;
|
|
overflow: hidden;
|
|
-moz-user-select: none;
|
|
}
|
|
music-view-stack > iframe {
|
|
border: none;
|
|
visibility: hidden;
|
|
pointer-events: none;
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
will-change: transform;
|
|
}
|
|
music-view-stack > iframe.active,
|
|
music-view-stack > iframe.fade,
|
|
music-view-stack > iframe.pop,
|
|
music-view-stack > iframe.push {
|
|
animation-duration: 0.2s;
|
|
animation-timing-function: linear;
|
|
visibility: visible;
|
|
}
|
|
music-view-stack > iframe.active {
|
|
pointer-events: auto;
|
|
}
|
|
music-view-stack > iframe.fade {
|
|
animation-duration: 0s;
|
|
}
|
|
music-view-stack > iframe.fade.in {
|
|
animation-name: fade-in;
|
|
}
|
|
music-view-stack > iframe.fade.out {
|
|
animation-name: fade-out;
|
|
}
|
|
music-view-stack > iframe.pop.in {
|
|
animation-name: pop-in;
|
|
}
|
|
music-view-stack > iframe.pop.out {
|
|
animation-name: pop-out;
|
|
}
|
|
music-view-stack > iframe.push.in {
|
|
animation-name: push-in;
|
|
}
|
|
music-view-stack > iframe.push.out {
|
|
animation-name: push-out;
|
|
}
|
|
[dir="rtl"] music-view-stack > iframe.pop.in {
|
|
animation-name: push-in;
|
|
}
|
|
[dir="rtl"] music-view-stack > iframe.pop.out {
|
|
animation-name: push-out;
|
|
}
|
|
[dir="rtl"] music-view-stack > iframe.push.in {
|
|
animation-name: pop-in;
|
|
}
|
|
[dir="rtl"] music-view-stack > iframe.push.out {
|
|
animation-name: pop-out;
|
|
}
|
|
@keyframes fade-in {
|
|
0% { opacity: 0; }
|
|
100% { opacity: 1; }
|
|
}
|
|
@keyframes fade-out {
|
|
0% { opacity: 1; }
|
|
100% { opacity: 0; }
|
|
}
|
|
@keyframes pop-in {
|
|
0% { transform: translateX(-100%); }
|
|
100% { transform: translateX(0); }
|
|
}
|
|
@keyframes pop-out {
|
|
0% { transform: translateX(0); }
|
|
100% { transform: translateX(100%); }
|
|
}
|
|
@keyframes push-in {
|
|
0% { transform: translateX(100%); }
|
|
100% { transform: translateX(0); }
|
|
}
|
|
@keyframes push-out {
|
|
0% { transform: translateX(0); }
|
|
100% { transform: translateX(-100%); }
|
|
}`;
|
|
|
|
if (this.firstChild) {
|
|
this.insertBefore(style, this.firstChild);
|
|
} else {
|
|
this.appendChild(style);
|
|
}
|
|
|
|
this.cachedViews = {};
|
|
|
|
[].forEach.call(this.querySelectorAll('iframe'), (frame) => {
|
|
var location = new URL(frame.src);
|
|
|
|
var view = {
|
|
url: location.pathname + location.search,
|
|
frame: frame,
|
|
title: frame.contentDocument.title
|
|
};
|
|
|
|
this.initializeView(view).then((view) => {
|
|
if (this.activeView === view) {
|
|
this.dispatchEvent(new CustomEvent('titlechange', {
|
|
detail: view.title
|
|
}));
|
|
}
|
|
});
|
|
|
|
this.cachedViews[view.url] = view;
|
|
});
|
|
|
|
this.addEventListener('animationend', (evt) => {
|
|
var classList = evt.target.classList;
|
|
if (classList.contains('destroy')) {
|
|
this.destroyView(evt.target.src);
|
|
}
|
|
|
|
classList.remove('fade', 'pop', 'push', 'in', 'out');
|
|
});
|
|
|
|
this.views = [];
|
|
this.activeView = null;
|
|
};
|
|
|
|
proto.loadView = function(url) {
|
|
var view = this.cachedViews[url];
|
|
if (view) {
|
|
return Promise.resolve(view);
|
|
}
|
|
|
|
var frame = document.createElement('iframe');
|
|
this.appendChild(frame);
|
|
frame.src = url;
|
|
|
|
view = {
|
|
url: url,
|
|
frame: frame,
|
|
title: ''
|
|
};
|
|
|
|
return this.initializeView(view);
|
|
};
|
|
|
|
proto.initializeView = function(view) {
|
|
view.frame.addEventListener('rendered', () => {
|
|
this.dispatchEvent(new CustomEvent('rendered'));
|
|
});
|
|
|
|
view.frame.addEventListener('titlechange', (evt) => {
|
|
view.title = evt.detail;
|
|
|
|
if (this.activeView === view) {
|
|
this.dispatchEvent(new CustomEvent('titlechange', {
|
|
detail: evt.detail
|
|
}));
|
|
}
|
|
});
|
|
|
|
const onL10nReady = () => {
|
|
view.title = view.frame.contentDocument.title;
|
|
this.cachedViews[view.url] = view;
|
|
this.dispatchEvent(new CustomEvent('loaded'));
|
|
return view;
|
|
};
|
|
|
|
return loaded(view.frame)
|
|
.then(() => view.frame.contentDocument.l10n.ready)
|
|
.then(onL10nReady);
|
|
};
|
|
|
|
proto.destroyView = function(url) {
|
|
for (var key in this.cachedViews) {
|
|
var view = this.cachedViews[key];
|
|
if (key === url || view.frame.src === url) {
|
|
view.frame.contentWindow.dispatchEvent(new CustomEvent('viewdestroy'));
|
|
delete this.cachedViews[key];
|
|
|
|
setTimeout(() => this.removeChild(view.frame), 5000);
|
|
return;
|
|
}
|
|
}
|
|
};
|
|
|
|
proto.setRootView = function(url) {
|
|
return this.loadView(url).then((view) => {
|
|
var oldActiveView = this.activeView;
|
|
if (oldActiveView) {
|
|
oldActiveView.frame.classList.add('fade', 'out');
|
|
oldActiveView.frame.classList.remove('active');
|
|
oldActiveView.frame.contentWindow.dispatchEvent(
|
|
new CustomEvent('viewhidden')
|
|
);
|
|
}
|
|
|
|
this.views = [view];
|
|
this.activeView = view;
|
|
|
|
var newActiveView = this.activeView;
|
|
newActiveView.frame.classList.add('fade', 'in');
|
|
newActiveView.frame.classList.add('active');
|
|
newActiveView.frame.contentWindow.dispatchEvent(
|
|
new CustomEvent('viewvisible')
|
|
);
|
|
|
|
this.dispatchEvent(new CustomEvent('change', { detail: newActiveView }));
|
|
});
|
|
};
|
|
|
|
proto.pushView = function(url) {
|
|
if (!this.activeView) {
|
|
return this.setRootView(url);
|
|
}
|
|
|
|
return this.loadView(url).then((view) => {
|
|
requestAnimationFrame(() => {
|
|
var oldActiveView = this.activeView;
|
|
oldActiveView.frame.classList.add('push', 'out');
|
|
oldActiveView.frame.classList.remove('active');
|
|
oldActiveView.frame.contentWindow.dispatchEvent(
|
|
new CustomEvent('viewhidden')
|
|
);
|
|
|
|
this.views.push(view);
|
|
this.activeView = view;
|
|
|
|
var newActiveView = this.activeView;
|
|
newActiveView.frame.classList.add('push', 'in', 'active');
|
|
newActiveView.frame.contentWindow.dispatchEvent(
|
|
new CustomEvent('viewvisible')
|
|
);
|
|
|
|
this.dispatchEvent(new CustomEvent('change', { detail: newActiveView }));
|
|
});
|
|
});
|
|
};
|
|
|
|
proto.popView = function(destroy) {
|
|
if (this.views.length < 2) {
|
|
return Promise.reject();
|
|
}
|
|
|
|
var oldActiveView = this.activeView;
|
|
|
|
this.views.pop();
|
|
this.activeView = this.views[this.views.length - 1];
|
|
|
|
var newActiveView = this.activeView;
|
|
|
|
return new Promise((resolve) => {
|
|
requestAnimationFrame(() => {
|
|
oldActiveView.frame.classList.add('pop', 'out');
|
|
oldActiveView.frame.classList.remove('active');
|
|
oldActiveView.frame.contentWindow.dispatchEvent(
|
|
new CustomEvent('viewhidden')
|
|
);
|
|
|
|
if (destroy) {
|
|
oldActiveView.frame.classList.add('destroy');
|
|
}
|
|
|
|
newActiveView.frame.classList.add('pop', 'in', 'active');
|
|
newActiveView.frame.contentWindow.dispatchEvent(
|
|
new CustomEvent('viewvisible')
|
|
);
|
|
|
|
this.dispatchEvent(new CustomEvent('change', { detail: newActiveView }));
|
|
|
|
resolve();
|
|
});
|
|
});
|
|
};
|
|
|
|
try {
|
|
window.MusicViewStack = document.registerElement('music-view-stack', {
|
|
prototype: proto
|
|
});
|
|
} catch (e) {
|
|
if (e.name !== 'NotSupportedError') {
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
function loaded(frame) {
|
|
return new Promise((resolve) => {
|
|
frame.addEventListener('load', function onLoad() {
|
|
frame.removeEventListener('load', onLoad);
|
|
resolve();
|
|
});
|
|
});
|
|
}
|
|
|
|
})(window);
|