зеркало из https://github.com/GoogleChrome/kino.git
Merge branch 'update/connection-status' into update/undo-removals
This commit is contained in:
Коммит
ab6262cfd4
|
@ -13,6 +13,7 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
</head>
|
||||
<body>
|
||||
<div id="offline-banner">No internet connection</div>
|
||||
<header class="_with-video">
|
||||
<div class="container">
|
||||
<h1><a data-use-router href="/">KINO</a></h1>
|
||||
|
@ -24,9 +25,6 @@
|
|||
<a data-use-router href="/">Home</a>
|
||||
<a data-use-router href="/downloads">Manage Downloads</a>
|
||||
<a data-use-router href="/settings">Settings</a>
|
||||
<span>
|
||||
Offline <toggle-button id="offline-content-only"></toggle-button> Online
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
@ -40,8 +38,6 @@
|
|||
</div>
|
||||
</footer>
|
||||
|
||||
<div id="connection-status"></div>
|
||||
|
||||
<script type="module" src="/dist/js/index.js"></script>
|
||||
|
||||
</body>
|
||||
|
|
|
@ -50,6 +50,27 @@ table {
|
|||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/**
|
||||
* Animations.
|
||||
*/
|
||||
@keyframes shake {
|
||||
10%, 90% {
|
||||
transform: translateX(-50.5%);
|
||||
}
|
||||
|
||||
20%, 80% {
|
||||
transform: translateX(-49%);
|
||||
}
|
||||
|
||||
30%, 50%, 70% {
|
||||
transform: translateX(-48%);
|
||||
}
|
||||
|
||||
40%, 60% {
|
||||
transform: translateX(-52%);
|
||||
}
|
||||
}
|
||||
|
||||
/* App Styles */
|
||||
html, body {
|
||||
margin: 0;
|
||||
|
@ -69,22 +90,30 @@ body {
|
|||
.tip {
|
||||
padding: 2rem;
|
||||
}
|
||||
#connection-status {
|
||||
#offline-banner {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: auto;
|
||||
opacity: 0;
|
||||
display: none;
|
||||
bottom: 1em;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 100;
|
||||
font: normal 0.8em sans-serif;
|
||||
padding: 0.5em 1em;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
color: #fff;
|
||||
padding: 0.5em;
|
||||
text-transform: uppercase;
|
||||
border-top-left-radius: 5px;
|
||||
}
|
||||
.online {
|
||||
background: #2eb872;
|
||||
}
|
||||
.offline {
|
||||
background: #fa4659;
|
||||
text-transform: uppercase;
|
||||
transition: opacity 200ms ease-in-out;
|
||||
white-space: pre;
|
||||
}
|
||||
[data-connection="offline"] #offline-banner {
|
||||
display: inline-block;
|
||||
opacity: 1;
|
||||
}
|
||||
#offline-banner.alert {
|
||||
animation: shake 0.6s cubic-bezier(.36,.07,.19,.97) both;
|
||||
}
|
||||
|
||||
h3 {
|
||||
|
@ -216,8 +245,8 @@ header .menu span {
|
|||
border-radius: 2rem;
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
header .menu toggle-button {
|
||||
padding: 0 1rem;
|
||||
header .menu offline-toggle-button {
|
||||
padding-right: 1rem;
|
||||
}
|
||||
header .container {
|
||||
padding: 2rem 0;
|
||||
|
|
59
src/index.js
59
src/index.js
|
@ -2,9 +2,8 @@
|
|||
* Router, Connection utils.
|
||||
*/
|
||||
import Router from './js/modules/Router.module';
|
||||
import updateOnlineStatus from './js/utils/updateOnlineStatus';
|
||||
import initializeGlobalToggle from './js/utils/initializeGlobalToggle';
|
||||
import VideoDownloaderRegistry from './js/modules/VideoDownloaderRegistry.module';
|
||||
import ConnectionStatus from './js/modules/ConnectionStatus.module';
|
||||
|
||||
/**
|
||||
* Web Components implementation.
|
||||
|
@ -14,6 +13,7 @@ import VideoCardComponent from './js/components/VideoCard';
|
|||
import VideoDownloaderComponent from './js/components/VideoDownloader';
|
||||
import VideoGrid from './js/components/VideoGrid';
|
||||
import ToggleButton from './js/components/ToggleButton';
|
||||
import OfflineToggleButton from './js/components/OfflineToggleButton';
|
||||
import ProgressRing from './js/components/ProgressRing';
|
||||
|
||||
/**
|
||||
|
@ -25,6 +25,12 @@ import CategoryPage from './js/pages/Category';
|
|||
import DownloadsPage from './js/pages/Downloads';
|
||||
import SettingsPage from './js/pages/Settings';
|
||||
|
||||
/**
|
||||
* Settings
|
||||
*/
|
||||
import { loadSetting } from './js/utils/settings';
|
||||
import { SETTING_KEY_TOGGLE_OFFLINE } from './js/constants';
|
||||
|
||||
/**
|
||||
* Custom Elements definition.
|
||||
*/
|
||||
|
@ -33,19 +39,55 @@ customElements.define('video-card', VideoCardComponent);
|
|||
customElements.define('video-downloader', VideoDownloaderComponent);
|
||||
customElements.define('video-grid', VideoGrid);
|
||||
customElements.define('toggle-button', ToggleButton);
|
||||
customElements.define('offline-toggle-button', OfflineToggleButton);
|
||||
customElements.define('progress-ring', ProgressRing);
|
||||
|
||||
/**
|
||||
* Tracks the connection status of the application and broadcasts
|
||||
* when the connections status changes.
|
||||
*/
|
||||
const offlineForced = loadSetting(SETTING_KEY_TOGGLE_OFFLINE) || false;
|
||||
const connectionStatus = new ConnectionStatus(offlineForced);
|
||||
const offlineBanner = document.querySelector('#offline-banner');
|
||||
|
||||
/**
|
||||
* Allow the page styling to respond to the global connection status.
|
||||
*
|
||||
* If an alert is emitted, slide in the "Not connected" message to inform
|
||||
* the user the action they attempted can't be performed right now.
|
||||
*/
|
||||
connectionStatus.subscribe(
|
||||
({ navigatorStatus, alert }) => {
|
||||
document.body.dataset.connection = navigatorStatus;
|
||||
|
||||
if (alert && navigatorStatus === 'offline') {
|
||||
offlineBanner.classList.add('alert');
|
||||
setTimeout(() => offlineBanner.classList.remove('alert'), 600);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* Initialize a registry holding instances of the `VideoDownload` web components.
|
||||
*
|
||||
* This is to allow us to share these instances between pages.
|
||||
*/
|
||||
const videoDownloaderRegistry = new VideoDownloaderRegistry();
|
||||
const videoDownloaderRegistry = new VideoDownloaderRegistry({ connectionStatus });
|
||||
|
||||
/**
|
||||
* Bind the offline toggle(s) to the `ConnectionStatus` instance.
|
||||
*/
|
||||
[...document.querySelectorAll('offline-toggle-button')].forEach(
|
||||
(button) => button.assignConnectionStatus(connectionStatus),
|
||||
);
|
||||
|
||||
/**
|
||||
* Router setup.
|
||||
*/
|
||||
const router = new Router({ videoDownloaderRegistry });
|
||||
const router = new Router({
|
||||
videoDownloaderRegistry,
|
||||
connectionStatus,
|
||||
});
|
||||
router.route('/', HomePage);
|
||||
router.route('/settings', SettingsPage);
|
||||
router.route('/downloads', DownloadsPage);
|
||||
|
@ -60,12 +102,3 @@ if ('serviceWorker' in navigator) {
|
|||
navigator.serviceWorker.register('sw.js');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Connection status.
|
||||
*/
|
||||
window.addEventListener('online', updateOnlineStatus);
|
||||
window.addEventListener('offline', updateOnlineStatus);
|
||||
updateOnlineStatus();
|
||||
|
||||
initializeGlobalToggle();
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
import { loadSetting, saveSetting, removeSetting } from '../utils/settings';
|
||||
import { SETTING_KEY_TOGGLE_OFFLINE } from '../constants';
|
||||
|
||||
import ToggleButton from './ToggleButton';
|
||||
|
||||
/**
|
||||
* Respond to button interaction.
|
||||
*
|
||||
* @param {boolean} forceOffline Force the offline state.
|
||||
* @param {ConnectionStatus} connectionStatus ConnectionStatus instance.
|
||||
*/
|
||||
const buttonInteractionHandler = (forceOffline, connectionStatus) => {
|
||||
if (forceOffline) {
|
||||
saveSetting(SETTING_KEY_TOGGLE_OFFLINE, true);
|
||||
connectionStatus.forceOffline();
|
||||
} else if (connectionStatus.getStatusDetail().navigatorStatus === 'offline') {
|
||||
/**
|
||||
* If we want to leave offline mode, but we're not online, let's
|
||||
* prevent the action and broadcast a "Not connected" alert instead.
|
||||
*/
|
||||
connectionStatus.alert();
|
||||
} else {
|
||||
removeSetting(SETTING_KEY_TOGGLE_OFFLINE);
|
||||
connectionStatus.unforceOffline();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Respond to connection status changes.
|
||||
*
|
||||
* @param {HTMLElement} button The toggle button element.
|
||||
* @param {ConnectionStatus} connectionStatus ConnectionStatus instance.
|
||||
*/
|
||||
const networkChangeHandler = (button, connectionStatus) => {
|
||||
const offlineModeEnabled = loadSetting(SETTING_KEY_TOGGLE_OFFLINE);
|
||||
|
||||
if (offlineModeEnabled) {
|
||||
button.checked = true;
|
||||
} else {
|
||||
button.checked = (connectionStatus.status === 'offline');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The offline toggle button is a special case of a toggle button
|
||||
* in a sense that it accepts and uses a `connectionStatus`
|
||||
* instance to help drive its logic.
|
||||
*/
|
||||
export default class OfflineToggleButton extends ToggleButton {
|
||||
constructor() {
|
||||
super();
|
||||
this.initialized = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ConnectionStatus} connectionStatus ConnectionStatus instance.
|
||||
*/
|
||||
assignConnectionStatus(connectionStatus) {
|
||||
if (this.initialized) return;
|
||||
|
||||
/**
|
||||
* On button interaction, we want to persist the state if it's
|
||||
* based on user gesture.
|
||||
*/
|
||||
this.$checkbox.addEventListener('change', (e) => {
|
||||
const forceOffline = (e.target.checked === true);
|
||||
buttonInteractionHandler(forceOffline, connectionStatus);
|
||||
});
|
||||
|
||||
/**
|
||||
* Respond to network changes.
|
||||
*/
|
||||
connectionStatus.subscribe(
|
||||
() => networkChangeHandler(this.$checkbox, connectionStatus),
|
||||
);
|
||||
|
||||
this.initialized = true;
|
||||
}
|
||||
}
|
|
@ -60,6 +60,7 @@ export default class ToggleButton extends HTMLElement {
|
|||
this.shadowRoot.appendChild(template.content.cloneNode(true));
|
||||
this.$checkbox = this._root.querySelector('input');
|
||||
this.$checkbox.addEventListener('change', (e) => {
|
||||
this.checked = e.target.checked;
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('change', { detail: { value: e.target.checked } }),
|
||||
);
|
||||
|
@ -71,7 +72,7 @@ export default class ToggleButton extends HTMLElement {
|
|||
}
|
||||
|
||||
get checked() {
|
||||
return this.getAttribute('checked');
|
||||
return this.getAttribute('checked') === 'true';
|
||||
}
|
||||
|
||||
set checked(value) {
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import { loadSetting } from '../utils/settings';
|
||||
|
||||
const style = `
|
||||
<style>
|
||||
:host {
|
||||
|
@ -54,36 +52,32 @@ const style = `
|
|||
}
|
||||
</style>`;
|
||||
|
||||
export default class extends HTMLElement {
|
||||
/**
|
||||
* When the connection status changes, enable or disable the card
|
||||
* with respect to the download state.
|
||||
*
|
||||
* @param {ConnectionStatus} connectionStatus ConnectionStatus instance.
|
||||
* @param {VideoDownloader} downloader `VideoDownloader` instance.
|
||||
*/
|
||||
function connectionStatusChangeHandler(connectionStatus, downloader) {
|
||||
if (connectionStatus.status === 'offline' && downloader.state !== 'done') {
|
||||
this.classList.add('disabled');
|
||||
} else {
|
||||
this.classList.remove('disabled');
|
||||
}
|
||||
}
|
||||
|
||||
export default class VideoCard extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
this._root = this.attachShadow({ mode: 'open' });
|
||||
|
||||
window.addEventListener('online', this.updateOnlineStatus.bind(this));
|
||||
window.addEventListener('online-mock', this.updateOnlineStatus.bind(this, { mock: true }));
|
||||
window.addEventListener('offline', this.updateOnlineStatus.bind(this));
|
||||
window.addEventListener('offline-mock', this.updateOnlineStatus.bind(this, { mock: false }));
|
||||
}
|
||||
|
||||
updateOnlineStatus(opts = {}) {
|
||||
const isOnline = opts.mock !== undefined ? opts.mock : navigator.onLine;
|
||||
const offlineContentOnly = loadSetting('offline-content-only');
|
||||
const isDownloaded = opts.downloader && (opts.downloader.state === 'done');
|
||||
if (((!isOnline || offlineContentOnly) && !isDownloaded)) {
|
||||
this.classList.add('disabled');
|
||||
} else {
|
||||
this.classList.remove('disabled');
|
||||
}
|
||||
}
|
||||
|
||||
attachDownloader(downloader) {
|
||||
downloader.onStatusUpdate = this.updateOnlineStatus.bind(this, { downloader });
|
||||
this._root.querySelector('.downloader').appendChild(downloader);
|
||||
this.updateOnlineStatus();
|
||||
}
|
||||
|
||||
render(videoData, navigate) {
|
||||
this.navigate = navigate;
|
||||
render({
|
||||
videoData,
|
||||
connectionStatus,
|
||||
downloader,
|
||||
}) {
|
||||
const templateElement = document.createElement('template');
|
||||
let posterImage = videoData.thumbnail;
|
||||
|
||||
|
@ -108,5 +102,15 @@ export default class extends HTMLElement {
|
|||
|
||||
const ui = templateElement.content.cloneNode(true);
|
||||
this._root.appendChild(ui);
|
||||
this._root.querySelector('.downloader').appendChild(downloader);
|
||||
|
||||
const boundHandler = connectionStatusChangeHandler.bind(this, connectionStatus, downloader);
|
||||
|
||||
/**
|
||||
* Whenever connection status or downloader state changes,
|
||||
* maybe disable / enable the card.
|
||||
*/
|
||||
connectionStatus.subscribe(boundHandler);
|
||||
downloader.subscribe(boundHandler);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -155,12 +155,14 @@ export default class extends HTMLElement {
|
|||
return ['state', 'progress', 'downloading', 'willremove'];
|
||||
}
|
||||
|
||||
constructor() {
|
||||
constructor({ connectionStatus }) {
|
||||
super();
|
||||
|
||||
// Attach Shadow DOM.
|
||||
this.internal = {};
|
||||
this.internal.root = this.attachShadow({ mode: 'open' });
|
||||
this.internal = {
|
||||
connectionStatus,
|
||||
changeCallbacks: [],
|
||||
root: this.attachShadow({ mode: 'open' }),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -180,10 +182,21 @@ export default class extends HTMLElement {
|
|||
}
|
||||
|
||||
set state(state) {
|
||||
const oldState = this.state;
|
||||
this.setAttribute('state', state);
|
||||
if (this.onStatusUpdate) {
|
||||
this.onStatusUpdate(state);
|
||||
}
|
||||
|
||||
this.internal.changeCallbacks.forEach(
|
||||
(callback) => callback(oldState, state),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to state changes.
|
||||
*
|
||||
* @param {Function} callback Callback function to run when the component's state changes.
|
||||
*/
|
||||
subscribe(callback) {
|
||||
this.internal.changeCallbacks.push(callback);
|
||||
}
|
||||
|
||||
get downloading() {
|
||||
|
|
|
@ -97,3 +97,8 @@ export const DEFAULT_AUDIO_PRIORITIES = [
|
|||
* These are all the types the Streamer has support for.
|
||||
*/
|
||||
export const ALL_STREAM_TYPES = ['audio', 'video'];
|
||||
|
||||
/**
|
||||
* Settings key names.
|
||||
*/
|
||||
export const SETTING_KEY_TOGGLE_OFFLINE = 'toggle-offline';
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
export default class ConnectionStatus {
|
||||
constructor(offlineForced = false) {
|
||||
this.internal = {
|
||||
status: navigator.onLine ? 'online' : 'offline',
|
||||
offlineForced,
|
||||
changeCallbacks: [],
|
||||
};
|
||||
|
||||
window.addEventListener('online', () => {
|
||||
this.internal.status = 'online';
|
||||
this.broadcast();
|
||||
});
|
||||
|
||||
window.addEventListener('offline', () => {
|
||||
this.internal.status = 'offline';
|
||||
this.broadcast();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the connection status, optionally overriden by the
|
||||
* offline status being forced.
|
||||
*
|
||||
* @returns {string} Connection status.
|
||||
*/
|
||||
get status() {
|
||||
return this.internal.offlineForced ? 'offline' : this.internal.status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle forced offline mode on.
|
||||
*/
|
||||
forceOffline() {
|
||||
this.internal.offlineForced = true;
|
||||
this.broadcast();
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle forced offline mode off.
|
||||
*/
|
||||
unforceOffline() {
|
||||
this.internal.offlineForced = false;
|
||||
this.broadcast();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns detailed information about the current status.
|
||||
*
|
||||
* @returns {object} Detailed information about the status.
|
||||
*/
|
||||
getStatusDetail() {
|
||||
return {
|
||||
status: this.status,
|
||||
navigatorStatus: this.internal.status,
|
||||
forcedOffline: this.internal.offlineForced,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to connection status changes.
|
||||
*
|
||||
* @param {Function} callback Callback function to run when connection status changes.
|
||||
*/
|
||||
subscribe(callback) {
|
||||
const detail = this.getStatusDetail();
|
||||
|
||||
this.internal.changeCallbacks.push(callback);
|
||||
callback(detail);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast the status to all subscribers and emits a global event
|
||||
* signalling the change.
|
||||
*
|
||||
* @param {object} detail Detail object to be broadcasted.
|
||||
*/
|
||||
broadcast(detail = null) {
|
||||
if (!detail) detail = this.getStatusDetail();
|
||||
|
||||
this.internal.changeCallbacks.forEach(
|
||||
(callback) => callback(detail),
|
||||
);
|
||||
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('connection-change', { detail }),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast the detail information with alert flag set to true in order
|
||||
* to indicate user action that couldn't be finished because the client is offline.
|
||||
*/
|
||||
alert() {
|
||||
const detail = this.getStatusDetail();
|
||||
detail.alert = true;
|
||||
|
||||
this.broadcast(detail);
|
||||
}
|
||||
}
|
|
@ -8,8 +8,9 @@ import VideoDownloader from '../components/VideoDownloader';
|
|||
* This helps maintain the component's state even across page loads.
|
||||
*/
|
||||
export default class VideoDownloaderRegistry {
|
||||
constructor() {
|
||||
constructor({ connectionStatus }) {
|
||||
this.instances = new Map();
|
||||
this.connectionStatus = connectionStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -20,7 +21,7 @@ export default class VideoDownloaderRegistry {
|
|||
* @returns {VideoDownloader} Instantiated VideoDownloader.
|
||||
*/
|
||||
create(videoId) {
|
||||
this.instances.set(videoId, new VideoDownloader());
|
||||
this.instances.set(videoId, new VideoDownloader({ connectionStatus: this.connectionStatus }));
|
||||
|
||||
return this.instances.get(videoId);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import getIDBConnection from '../modules/IDBConnection.module';
|
||||
import { SW_CACHE_NAME } from '../constants';
|
||||
import getDownloaderElement from '../utils/getDownloaderElement.module';
|
||||
|
||||
/**
|
||||
* @param {RouterContext} routerContext Context object passed by the Router.
|
||||
|
@ -9,6 +9,7 @@ export default async (routerContext) => {
|
|||
mainContent,
|
||||
apiData,
|
||||
navigate,
|
||||
connectionStatus,
|
||||
videoDownloaderRegistry,
|
||||
} = routerContext;
|
||||
mainContent.innerHTML = `
|
||||
|
@ -53,14 +54,15 @@ export default async (routerContext) => {
|
|||
allMeta.forEach((meta) => {
|
||||
const videoData = apiData.find((vd) => vd.id === meta.videoId);
|
||||
const card = document.createElement('video-card');
|
||||
let downloader = videoDownloaderRegistry.get(videoData.id);
|
||||
if (!downloader) {
|
||||
downloader = videoDownloaderRegistry.create(videoData.id);
|
||||
downloader.init(videoData, SW_CACHE_NAME);
|
||||
}
|
||||
downloader.setAttribute('expanded', 'false');
|
||||
card.render(videoData, navigate);
|
||||
card.attachDownloader(downloader);
|
||||
const downloader = getDownloaderElement(videoDownloaderRegistry, videoData);
|
||||
|
||||
card.render({
|
||||
videoData,
|
||||
navigate,
|
||||
connectionStatus,
|
||||
downloader,
|
||||
});
|
||||
|
||||
grid.appendChild(card);
|
||||
});
|
||||
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
import { saveSetting, loadSetting } from '../utils/settings';
|
||||
|
||||
const onChange = (key) => ({ detail }) => {
|
||||
saveSetting(key, detail.value);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {RouterContext} routerContext Context object passed by the Router.
|
||||
*/
|
||||
export default (routerContext) => {
|
||||
const { mainContent } = routerContext;
|
||||
const {
|
||||
mainContent,
|
||||
connectionStatus,
|
||||
} = routerContext;
|
||||
mainContent.innerHTML = `
|
||||
<div class="page-title">
|
||||
<h2>Settings</h2>
|
||||
|
@ -16,7 +13,7 @@ export default (routerContext) => {
|
|||
</div>
|
||||
<div class="settings">
|
||||
<div class="option">
|
||||
<toggle-button id="offline-content-only"></toggle-button>
|
||||
<offline-toggle-button></offline-toggle-button>
|
||||
<div>
|
||||
<h4>Show offline content only</h4>
|
||||
<p>When enabled, you will only be shown content that is available offline.</p>
|
||||
|
@ -31,14 +28,8 @@ export default (routerContext) => {
|
|||
<!-- </div>-->
|
||||
</div>
|
||||
`;
|
||||
const toggleButtonOffline = mainContent.querySelector('toggle-button#offline-content-only');
|
||||
toggleButtonOffline.addEventListener('change', onChange('offline-content-only'));
|
||||
|
||||
// TODO: Listen for global toggle change and sync this local setting?
|
||||
// Should we enable and gray-out that option while offline?
|
||||
// +auto enable when going offline on this page?
|
||||
const isOffline = !navigator.onLine;
|
||||
if (loadSetting('offline-content-only') || isOffline) {
|
||||
toggleButtonOffline.checked = true;
|
||||
}
|
||||
mainContent
|
||||
.querySelector('offline-toggle-button')
|
||||
.assignConnectionStatus(connectionStatus);
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { SW_CACHE_NAME } from '../constants';
|
||||
import getDownloaderElement from './getDownloaderElement.module';
|
||||
|
||||
/**
|
||||
* @param {RouterContext} routerContext Context passed through by Router.
|
||||
|
@ -10,6 +10,7 @@ function appendVideoToGallery(routerContext, localContext) {
|
|||
apiData,
|
||||
navigate,
|
||||
mainContent,
|
||||
connectionStatus,
|
||||
} = routerContext;
|
||||
|
||||
const category = localContext.category || '';
|
||||
|
@ -23,18 +24,15 @@ function appendVideoToGallery(routerContext, localContext) {
|
|||
|
||||
apiData.forEach((videoData) => {
|
||||
const card = document.createElement('video-card');
|
||||
const downloader = getDownloaderElement(videoDownloaderRegistry, videoData);
|
||||
|
||||
let downloader = videoDownloaderRegistry.get(videoData.id);
|
||||
if (!downloader) {
|
||||
downloader = videoDownloaderRegistry.create(videoData.id);
|
||||
downloader.init(videoData, SW_CACHE_NAME);
|
||||
}
|
||||
downloader.setAttribute('expanded', 'false');
|
||||
card.render({
|
||||
videoData,
|
||||
navigate,
|
||||
connectionStatus,
|
||||
downloader,
|
||||
});
|
||||
|
||||
const player = document.createElement('video-player');
|
||||
card.render(videoData, navigate);
|
||||
card.attachDownloader(downloader);
|
||||
player.render(videoData);
|
||||
videoGallery.appendChild(card);
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
import { SW_CACHE_NAME } from '../constants';
|
||||
|
||||
/**
|
||||
* Returns a `VideoDownloader` object for a given video ID.
|
||||
*
|
||||
* @param {VideoDownloaderRegistry} videoDownloaderRegistry Registry.
|
||||
* @param {object} videoData Video data.
|
||||
*
|
||||
* @returns {VideoDownloader} `VideoDownloader` instance.
|
||||
*/
|
||||
export default (videoDownloaderRegistry, videoData) => {
|
||||
let downloader = videoDownloaderRegistry.get(videoData.id);
|
||||
if (!downloader) {
|
||||
downloader = videoDownloaderRegistry.create(videoData.id);
|
||||
downloader.init(videoData, SW_CACHE_NAME);
|
||||
}
|
||||
downloader.setAttribute('expanded', 'false');
|
||||
|
||||
return downloader;
|
||||
};
|
|
@ -1,32 +0,0 @@
|
|||
import { loadSetting, saveSetting } from './settings';
|
||||
|
||||
const onChange = (key) => ({ detail }) => {
|
||||
saveSetting(key, !detail.value);
|
||||
window.dispatchEvent(new CustomEvent(`${!detail.value ? 'offline' : 'online'}-mock`));
|
||||
};
|
||||
|
||||
/**
|
||||
* Update online status for header toggle
|
||||
*/
|
||||
function updateOnlineStatus() {
|
||||
const toggleButtonOffline = document.querySelector('header toggle-button#offline-content-only');
|
||||
toggleButtonOffline.checked = navigator.onLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the offline/online toggle from the header.
|
||||
*/
|
||||
export default function initializeGlobalToggle() {
|
||||
const toggleButtonOffline = document.querySelector('header toggle-button#offline-content-only');
|
||||
toggleButtonOffline.addEventListener('change', onChange('offline-content-only'));
|
||||
|
||||
window.addEventListener('online', updateOnlineStatus);
|
||||
window.addEventListener('offline', updateOnlineStatus);
|
||||
|
||||
// Should we enable and gray-out that option while offline?
|
||||
// +auto enable when going offline on this page?
|
||||
const isOnline = navigator.onLine;
|
||||
if (!loadSetting('offline-content-only') || isOnline) {
|
||||
toggleButtonOffline.checked = true;
|
||||
}
|
||||
}
|
|
@ -13,3 +13,12 @@ export function saveSetting(key, value) {
|
|||
export function loadSetting(key) {
|
||||
return JSON.parse(localStorage.getItem(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a settings entry.
|
||||
*
|
||||
* @param {string} key Setting key.
|
||||
*/
|
||||
export function removeSetting(key) {
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
import { saveSetting } from './settings';
|
||||
|
||||
/**
|
||||
* Update online status helper.
|
||||
*/
|
||||
export default function updateOnlineStatus() {
|
||||
const status = document.getElementById('connection-status');
|
||||
const condition = navigator.onLine ? 'online' : 'offline';
|
||||
status.className = condition;
|
||||
status.innerHTML = condition;
|
||||
|
||||
// If we want to sync the setting with actual connection state
|
||||
saveSetting('offline-content-only', condition === 'offline');
|
||||
}
|
Загрузка…
Ссылка в новой задаче