Skip to content

Commit

Permalink
Merge pull request #99 from xwp/update/undo-removals
Browse files Browse the repository at this point in the history
Add the undo button to cancel video removal
  • Loading branch information
derekherman authored Mar 18, 2021
2 parents d08e337 + 141d344 commit 1f07bb4
Show file tree
Hide file tree
Showing 18 changed files with 450 additions and 169 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"max-classes-per-file": "off",
"prefer-promise-reject-errors": "off",
"class-methods-use-this": "off",
"jsdoc/no-undefined-types": "off"
"jsdoc/no-undefined-types": "off",
"no-restricted-syntax": "off"
}
}
6 changes: 1 addition & 5 deletions public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -24,9 +25,6 @@ <h1><a data-use-router href="/">KINO</a></h1>
<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>
Expand All @@ -40,8 +38,6 @@ <h1><a data-use-router href="/">KINO</a></h1>
</div>
</footer>

<div id="connection-status"></div>

<script type="module" src="/dist/js/index.js"></script>

</body>
Expand Down
67 changes: 49 additions & 18 deletions public/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
background: #fa4659;
text-transform: uppercase;
border-top-left-radius: 5px;
transition: opacity 200ms ease-in-out;
white-space: pre;
}
.online {
background: #2eb872;
[data-connection="offline"] #offline-banner {
display: inline-block;
opacity: 1;
}
.offline {
background: #fa4659;
#offline-banner.alert {
animation: shake 0.6s cubic-bezier(.36,.07,.19,.97) both;
}

h3 {
Expand Down Expand Up @@ -127,7 +156,8 @@ header {
padding-bottom: 56.25%; /* 16:9 */
position: relative;
}
.poster-image picture img {
.poster-image picture img,
.poster-image > img {
width: 100%;
height: auto;
}
Expand Down Expand Up @@ -163,7 +193,7 @@ header.with-video {
height: auto;
}
.poster-wrapper .info {
padding: 2rem 2rem 0 2rem;
padding: 2rem 0;
}
.poster-wrapper .has-player .info {
display: none;
Expand Down Expand Up @@ -216,11 +246,11 @@ 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;
padding: 2rem;
display: flex;
justify-content: space-between;
align-items: center;
Expand Down Expand Up @@ -271,7 +301,7 @@ header .hamburger, header .close {

article {
background: #FFF;
padding: 4rem 2rem;
padding: 4rem 0;
}
article h2 {
font-size: 2rem;
Expand Down Expand Up @@ -319,8 +349,9 @@ article p {
}

.container {
max-width: 1200px;
max-width: calc(1200px + 4rem);
margin: 0 auto;
padding: 0 2rem;
}

div.card {
Expand Down Expand Up @@ -360,7 +391,7 @@ div.card {
footer {
background: var(--background-dark);
color: var(--text-footer);
padding: 2rem;
padding: 2rem 0;
font-size: 1rem;
}
footer a {
Expand Down
59 changes: 46 additions & 13 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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';

/**
Expand All @@ -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.
*/
Expand All @@ -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);
Expand All @@ -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();
79 changes: 79 additions & 0 deletions src/js/components/OfflineToggleButton.js
Original file line number Diff line number Diff line change
@@ -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;
}
}
3 changes: 2 additions & 1 deletion src/js/components/ToggleButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 } }),
);
Expand All @@ -71,7 +72,7 @@ export default class ToggleButton extends HTMLElement {
}

get checked() {
return this.getAttribute('checked');
return this.getAttribute('checked') === 'true';
}

set checked(value) {
Expand Down
Loading

0 comments on commit 1f07bb4

Please sign in to comment.