Skip to content

Commit

Permalink
Simplify Overlay close animation handling
Browse files Browse the repository at this point in the history
Followed suggestions from the Mithril documentation.
https://mithril.js.org/animation.html#animation-on-element-removal
  • Loading branch information
rezbyte authored and charlag committed Feb 13, 2024
1 parent 643b249 commit 6067854
Show file tree
Hide file tree
Showing 5 changed files with 25 additions and 34 deletions.
2 changes: 1 addition & 1 deletion src/gui/SearchInPageOverlay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ assertMainOrNode()
* gets loaded asynchronously, shouldn't be in the web bundle
*/
export class SearchInPageOverlay {
private closeFunction: (() => Promise<void>) | null
private closeFunction: (() => void) | null
private domInput!: HTMLInputElement
private matchCase: boolean = false
private numberOfMatches: number = 0
Expand Down
42 changes: 18 additions & 24 deletions src/gui/base/Overlay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import m, { Children, Component, VnodeDOM } from "mithril"
import { LayerType } from "../../RootView"
import type { lazy } from "@tutao/tutanota-utils"
import { assertMainOrNodeBoot } from "../../api/common/Env"
import { ProgrammingError } from "../../api/common/error/ProgrammingError.js"
import { px, size } from "../size.js"
import { styles } from "../styles.js"
import { getSafeAreaInsetBottom } from "../HtmlUtils.js"
import { ProgrammingError } from "../../api/common/error/ProgrammingError.js"

assertMainOrNodeBoot()
export type PositionRect = {
Expand All @@ -23,7 +23,6 @@ type OverlayAttrs = {
createAnimation?: string
closeAnimation?: string
shadowClass: string
isClosing: boolean
}

const overlays: Map<number, OverlayAttrs> = new Map()
Expand All @@ -35,7 +34,7 @@ export function displayOverlay(
createAnimation?: string,
closeAnimation?: string,
shadowClass: string = "dropdown-shadow",
): () => Promise<void> {
): () => void {
// Use the inverse of the show animation as the close animation if it is not given
if (createAnimation != null && closeAnimation == null) closeAnimation = `${createAnimation} animation-reverse`

Expand All @@ -46,21 +45,15 @@ export function displayOverlay(
createAnimation,
closeAnimation,
shadowClass,
isClosing: false,
} as OverlayAttrs
// Add the new overlay into the overlay container
overlays.set(overlayKey, pair)

return async () => {
// Get the overlay we added in the above function body
const overlay: OverlayAttrs | null = overlays.get(overlayKey) ?? null
if (overlay == null) throw new ProgrammingError(`Failed to remove overlay with key:${overlayKey}!`)

// Switch its animation CSS class to `closeAnimation`
overlay.isClosing = true

// Return a promise to maintain compatibility with legacy code
return Promise.resolve()
return () => {
// Remove the overlay & error if unsuccessful
if (!overlays.delete(overlayKey)) {
throw new ProgrammingError(`Failed to remove overlay with key:${overlayKey}!`)
}
}
}

Expand All @@ -87,8 +80,7 @@ export const overlay: Component = {
const position = attrs.position()

const baseClasses = "abs elevated-bg " + attrs.shadowClass
const currentAnimation = attrs.isClosing ? attrs.closeAnimation : attrs.createAnimation
const classes = currentAnimation == null ? baseClasses : baseClasses + " " + currentAnimation
const classes = attrs.createAnimation == null ? baseClasses : baseClasses + " " + attrs.createAnimation

return m(
"",
Expand All @@ -104,19 +96,21 @@ export const overlay: Component = {
height: position.height,
"z-index": position.zIndex != null ? position.zIndex : LayerType.Overlay,
},
onanimationend: () => {
if (attrs.isClosing) {
overlays.delete(key)
}
},
onupdate(vnode: VnodeDOM<any>): any {
if (attrs.isClosing && attrs.closeAnimation != null) {
onbeforeremove: (vnode: VnodeDOM) => {
if (attrs.closeAnimation != null) {
const dom = vnode.dom as HTMLElement

// Force the environment to restart the animations via a reflow
dom.className = baseClasses
void dom.offsetWidth
dom.className = classes

// Play the closing animation
dom.className = baseClasses + " " + attrs.closeAnimation

// Wait for the close animation to complete
return new Promise(function (resolve) {
dom.addEventListener("animationend", resolve)
})
}
},
},
Expand Down
4 changes: 2 additions & 2 deletions src/mail/model/MinimizedMailEditorViewModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export type MinimizedEditor = {
dispose: () => void
// disposes dialog and templatePopup eventListeners when minimized mail is removed
saveStatus: Stream<SaveStatus>
closeOverlayFunction: () => Promise<void>
closeOverlayFunction: () => void
}

/**
Expand All @@ -53,7 +53,7 @@ export class MinimizedMailEditorViewModel {
sendMailModel: SendMailModel,
dispose: () => void,
saveStatus: Stream<SaveStatus>,
closeOverlayFunction: () => Promise<void>,
closeOverlayFunction: () => void,
): MinimizedEditor {
dialog.close()

Expand Down
9 changes: 3 additions & 6 deletions src/mail/view/MinimizedMailEditorOverlay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { MinimizedMailEditorViewModel } from "../model/MinimizedMailEditorViewMo
import { MinimizedEditorOverlay } from "./MinimizedEditorOverlay"
import { assertMainOrNode } from "../../api/common/Env"
import Stream from "mithril/stream"
import { noOp } from "@tutao/tutanota-utils"

assertMainOrNode()
const MINIMIZED_OVERLAY_WIDTH_WIDE = 350
Expand All @@ -26,7 +27,7 @@ export function showMinimizedMailEditor(
dispose: () => void,
saveStatus: Stream<SaveStatus>,
): void {
let closeOverlayFunction = () => Promise.resolve() // will be assigned with the actual close function when overlay is visible.
let closeOverlayFunction: () => void = noOp // will be assigned with the actual close function when overlay is visible.

const minimizedEditor = viewModel.minimizeMailEditor(dialog, sendMailModel, dispose, saveStatus, () => closeOverlayFunction())
// only show overlay once editor is gone
Expand All @@ -35,11 +36,7 @@ export function showMinimizedMailEditor(
}, DefaultAnimationTime)
}

function showMinimizedEditorOverlay(
viewModel: MinimizedMailEditorViewModel,
minimizedEditor: MinimizedEditor,
eventController: EventController,
): () => Promise<void> {
function showMinimizedEditorOverlay(viewModel: MinimizedMailEditorViewModel, minimizedEditor: MinimizedEditor, eventController: EventController): () => void {
return displayOverlay(
() => getOverlayPosition(),
{
Expand Down
2 changes: 1 addition & 1 deletion src/search/SearchBar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export class SearchBar implements Component<SearchBarAttrs> {
private readonly state: Stream<SearchBarState>
busy: boolean = false
private lastSelectedWhitelabelChildrenInfoResult: Stream<WhitelabelChild> = stream()
private closeOverlayFunction: (() => Promise<void>) | null = null
private closeOverlayFunction: (() => void) | null = null
private readonly overlayContentComponent: Component
private confirmDialogShown: boolean = false
private domWrapper!: HTMLElement
Expand Down

0 comments on commit 6067854

Please sign in to comment.