Skip to content

Commit

Permalink
Smoother overlay transitions
Browse files Browse the repository at this point in the history
Two main Overlay behavior changes:
* When transitioning between overlays, don't force the caller to
  manually dismiss the old overlay, wait for it go away, and then launch
  the new one in order to avoid jarring visual transitions. Instead,
  create a built-in system that detects when we're already showing the
  overlay, fade out the contents, replace it with the new content, and
  then fade in the new content.
* Use the built-in `.animate()`. Don't rely on a janky custom animation
  library I created almost a decade ago for a different random
  side-project. Ideally I'll be able to remove Animation.js entirely at
  some point, but other classes use it much more heavily than Overlay
  did.
  • Loading branch information
danrahn committed Nov 5, 2023
1 parent 00a66ee commit fd12589
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 92 deletions.
5 changes: 2 additions & 3 deletions Client/Script/ClientSettings.js
Original file line number Diff line number Diff line change
Expand Up @@ -620,14 +620,13 @@ class ClientSettingsUI {
}

/**
* Dismisses the current overlay and brings in a new one.
* Replace the current overlay with a new one.
* @param {HTMLElement} newOverlayContainer The new overlay to display.
* @param {OverlayOptions?} [options={}] The new overlay's options, if any. */
#transitionOverlay(newOverlayContainer, options={}) {
// Don't mess with focusBack. null == don't overwrite, undefined == reset.
options.focusBack = null;
Overlay.dismiss(true /*forReshow*/);
setTimeout(() => Overlay.build(options, newOverlayContainer), 250);
Overlay.build(options, newOverlayContainer);
}

/** Enables or disabled a dialog setting
Expand Down
84 changes: 31 additions & 53 deletions Client/Script/SectionOptionsOverlay.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,13 @@ class SectionOptionsOverlay {
* @param {HTMLElement} focusBack */
show(focusBack) {
this.#focusBack = focusBack;
this.#showMain(false /*needsTransition*/);
this.#showMain();
}

/**
* Display the main overlay, either for the first time, or as the result of
* canceling out of a specific option.
* @param {boolean} needsTransition Whether an overlay is already showing, so we should smoothly transition between overlays. */
#showMain(needsTransition) {
* canceling out of a specific option. */
#showMain() {
const container = buildNode('div', { class : 'sectionOptionsOverlayContainer' });
appendChildren(container,
buildNode('h1', {}, 'Section Options'),
Expand All @@ -54,11 +53,7 @@ class SectionOptionsOverlay {
);

const options = { dismissible : true, focusBack : this.#focusBack, noborder : true, closeButton : true };
if (needsTransition) {
this.#transitionOverlay(container, options);
} else {
Overlay.build(options, container);
}
Overlay.build(options, container);
}

/**
Expand All @@ -81,11 +76,11 @@ class SectionOptionsOverlay {
{ id : 'exportConfirmBtn', class : 'overlayButton confirmSetting' }),
ButtonCreator.textButton(
'Back',
function () { this.#showMain(true); }.bind(this),
this.#showMain.bind(this),
{ class : 'overlayButton' })));

Tooltip.setTooltip($$('label', container), 'Export markers from the entire server, not just the active library.');
this.#transitionOverlay(container, { dismissible : true, focusBack : this.#focusBack });
Overlay.build({ dismissible : true, focusBack : this.#focusBack }, container);
}

/**
Expand Down Expand Up @@ -129,11 +124,11 @@ class SectionOptionsOverlay {
{ id : 'exportConfirmBtn', class : 'overlayButton confirmSetting' }),
ButtonCreator.textButton(
'Back',
function () { this.#showMain(true); }.bind(this),
this.#showMain.bind(this),
{ class : 'overlayButton' }))
);

this.#transitionOverlay(container, { dismissible : true, focusBack : this.#focusBack });
Overlay.build({ dismissible : true, focusBack : this.#focusBack }, container);
}

/**
Expand Down Expand Up @@ -164,21 +159,18 @@ class SectionOptionsOverlay {
$('#applyGlobally').checked ? -1 : PlexClientState.activeSection(),
$('#resolutionType').value);

Overlay.dismiss(true /*forReshow*/);
setTimeout(() => {
Overlay.show(
`<h2>Marker Import Succeeded</h2><hr>` +
`Markers Added: ${result.added}<br>` +
`Ignored Markers (identical): ${result.identical}<br>` +
`Ignored Markers (merge/ignore/self-overlap): ${result.ignored}<br>` +
`Existing Markers Deleted (overwritten): ${result.deleted}<br>` +
`Existing Markers Modified (merged): ${result.modified}<br>`,
'Reload',
// Easier to just reload the page instead of reconciling all the newly deleted markers
() => { window.location.reload(); },
false /*dismissible*/);
Overlay.setFocusBackElement(this.#focusBack);
}, 250);
await Overlay.show(
`<h2>Marker Import Succeeded</h2><hr>` +
`Markers Added: ${result.added}<br>` +
`Ignored Markers (identical): ${result.identical}<br>` +
`Ignored Markers (merge/ignore/self-overlap): ${result.ignored}<br>` +
`Existing Markers Deleted (overwritten): ${result.deleted}<br>` +
`Existing Markers Modified (merged): ${result.modified}<br>`,
'Reload',
// Easier to just reload the page instead of reconciling all the newly deleted markers
() => { window.location.reload(); },
false /*dismissible*/);
Overlay.setFocusBackElement(this.#focusBack);
} catch (err) {
errorResponseOverlay('Failed to upload and apply markers', err);
}
Expand All @@ -197,7 +189,7 @@ class SectionOptionsOverlay {

const cancelButton = ButtonCreator.textButton(
'Back',
function () { this.#showMain(true); }.bind(this),
this.#showMain.bind(this),
{ id : 'deleteMarkerCancel', class : 'overlayButton' });

warnText.appendChild(
Expand All @@ -223,9 +215,7 @@ class SectionOptionsOverlay {
buildNode('h2', {}, 'DANGER ZONE'),
buildNode('hr'),
warnText);
this.#transitionOverlay(
container,
{ dismissible : true, focusBack : this.#focusBack });
Overlay.build({ dismissible : true, focusBack : this.#focusBack }, container);
}

/**
Expand All @@ -241,32 +231,20 @@ class SectionOptionsOverlay {
const deleteType = parseInt($('#deleteAllTypeSelect').value);
try {
const result = await ServerCommand.sectionDelete(PlexClientState.activeSection(), deleteType);
Overlay.dismiss(true /*forReshow*/);
setTimeout(() => {
Overlay.show(
`<h2>Section Delete Succeeded</h2><hr>` +
`Markers Deleted: ${result.deleted}<br>` +
`Backup Entries Removed: ${result.backupDeleted}<br>`,
'Reload',
// Easier to just reload the page instead of reconciling all the newly deleted markers
() => { window.location.reload(); },
false /*dismissible*/);
Overlay.setFocusBackElement(this.#focusBack);
}, 250);
await Overlay.show(
`<h2>Section Delete Succeeded</h2><hr>` +
`Markers Deleted: ${result.deleted}<br>` +
`Backup Entries Removed: ${result.backupDeleted}<br>`,
'Reload',
// Easier to just reload the page instead of reconciling all the newly deleted markers
() => { window.location.reload(); },
false /*dismissible*/);
Overlay.setFocusBackElement(this.#focusBack);
} catch (err) {
errorResponseOverlay('Failed to delete section markers.', err);
}
}

/**
* Dismiss the current overlay and immediately replace it with a new one.
* @param {HTMLElement} container
* @param {OverlayOptions} options */
#transitionOverlay(container, options) {
Overlay.dismiss(true /*forReshow*/);
setTimeout(() => { Overlay.build(options, container); }, 250);
}

/**
* Flash the background of the given element.
* @param {HTMLElement} input */
Expand Down

0 comments on commit fd12589

Please sign in to comment.