~sirn/fanboi2

ref: a68873a108d20a6a49a1b6caf374bfb2cf6a94fe fanboi2/assets/app/javascripts/components/board_selector.ts -rw-r--r-- 4.4 KiB
a68873a1Kridsada Thanabulpong Coding style cleanups and setup pre-commit hooks (#42) 3 years ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import { VNode, create, diff, h, patch } from "virtual-dom";

import { SingletonComponent } from "./base";
import { Board } from "../models/board";
import { BoardSelectorView } from "../views/board_selector_view";
import { addClass } from "../utils/elements";

const animationDuration = 200;

export class BoardSelector extends SingletonComponent {
    public targetSelector = "[data-board-selector]";

    protected bindOne($element: Element): void {
        let $button: Element;
        let $selector: Element | undefined;
        let selectorView: BoardSelectorView | undefined;
        let selectorNode: VNode | undefined;
        let selectorState: boolean = false;
        let throttleTimer: number;

        let _render = (height: number = 0): Promise<any> => {
            if (!$selector) {
                return Board.queryAll().then((boards: Board[]) => {
                    selectorView = new BoardSelectorView(boards);
                    selectorNode = selectorView.render();
                    $selector = create(selectorNode);
                    document.body.insertBefore($selector, $element.nextSibling);
                });
            } else {
                return new Promise(resolve => {
                    resolve();
                });
            }
        };

        let _update = (height: number): void => {
            if ($selector && selectorView && selectorNode) {
                let newSelectorNode = selectorView.render({
                    style: {
                        height: `${height}px`,
                    },
                });

                let patches = diff(selectorNode, newSelectorNode);
                $selector = patch($selector, patches);
                selectorNode = newSelectorNode;
            }
        };

        let _animate = (updateFn: ((elapsedPercent: number) => void)) => {
            let startTime: number = 0;
            let _animateStep = (time: number) => {
                if (!startTime) {
                    startTime = time;
                }

                let elapsed = Math.min(time - startTime, animationDuration);
                let elapsedPercent = elapsed / animationDuration;

                updateFn(elapsedPercent);

                if (elapsed < animationDuration) {
                    requestAnimationFrame(_animateStep);
                }
            };

            requestAnimationFrame(_animateStep);
        };

        $button = create(
            h("div", { className: "js-board-selector-button" }, [
                h("a", { href: "#" }, ["Boards"]),
            ]),
        );

        $button.addEventListener("click", e => {
            e.preventDefault();
            _render().then(() => {
                if ($selector) {
                    let selectorHeight = BoardSelector.getSelectorHeight($selector);

                    if (selectorState) {
                        selectorState = false;
                        _animate((elapsedPercent: number) => {
                            _update(selectorHeight * (1 - elapsedPercent));
                        });
                    } else {
                        selectorState = true;
                        _animate((elapsedPercent: number) => {
                            _update(selectorHeight * elapsedPercent);
                        });
                    }
                }
            });
        });

        $element.querySelector(".container").appendChild($button);
        addClass($element, ["js-board-selector-wrapper"]);

        // Attempt to restore height on resize. Since the resize may cause
        // clientHeight to change (and will cause the board selector to be
        // clipped or has extra whitespace).
        //
        // Do nothing if resize was called before board selector was attached
        // or if selector was attached but not displayed.
        window.addEventListener("resize", (e: Event) => {
            clearTimeout(throttleTimer);
            throttleTimer = setTimeout(() => {
                if ($selector && selectorState) {
                    _update(BoardSelector.getSelectorHeight($selector));
                    selectorState = true;
                }
            }, 100);
        });
    }

    private static getSelectorHeight($selector: Element): number {
        let $el = $selector.querySelector(".js-board-selector-inner");

        if ($el) {
            return $el.clientHeight;
        }

        throw new Error("Could not retrieve board selector height.");
    }
}