1/*  DokuWiki MoaiEditor Scroll_sync.js file
2    Author  : MoaiTools <info@moaitools.org>
3    License : GPL 3 (http://www.gnu.org/licenses/gpl.html) */
4
5/*  Scrolls the preview (keeping it synchronized with the editor).
6
7*/
8MoaiEditor.ScrollSync = class {
9
10    constructor(outer) {
11
12        // Variables
13        this.disabled = false;      // synchronization can be disabled temporarily when other processes want to take control
14    }
15
16    init() {
17        this.scroller = new MoaiEditor.Scroller(this);
18    }
19
20    get isEnabled() {
21        if (this.disabled  ||  moaiEditor.buttons.autoscroll.mode == 'off'  ||  !moaiEditor.layoutReady)
22            return false;
23        return true;
24    }
25
26    // This function is called whenever the user scrolls the editor side
27    onScroll(event) {
28
29        // Exit if disabled by the user, by other process, or if the editor is not ready
30        if (!this.isEnabled)
31            return;
32
33        // Exit if the editor scroll has not changed (to avoid interfering right after another process has done scrolling)
34        const scroll = moaiEditor.editor.current.scroll.top;
35        const sameScroll = Math.abs(scroll - this.lastScroll) <= 2;
36        if (sameScroll)
37            return;
38
39        // Synchronize the scroll of the preview
40        const calc = this.calcScroll();
41        if (calc !== null) {
42            this.scroller.target = calc.right;
43            this.scroller.engaged = true;
44        }
45        // Remember last scroll position
46        this.lastScroll = scroll;
47    }
48
49    calcScroll() {
50
51        // There should be at least one match
52        var matches = moaiEditor.matches.matches;
53        if (matches.length == 0)
54            return null;
55
56        // Get the editor side scroll position
57        const leftScroll = moaiEditor.editor.current.scroll.top;
58
59        // Get the preview container top position relative to the screen
60        this.container = moaiEditor.preview.container;
61        this.containerTop = this.container.getBoundingClientRect().top;
62
63        // Get the scroll points above and below our current scroll position
64        var p1, p2, match;
65        for (var i = 0; i < matches.length; i++) {
66            if (matches[i].scroll === null)
67                continue;
68            match = matches[i];
69            if (match.scroll.top > leftScroll) {
70                p1 = this.getPoint(i-1, 'bottom');
71                p2 = this.getPoint(i, 'top');
72                break;
73            }
74            if (match.scroll.bottom > leftScroll) {
75                p1 = this.getPoint(i, 'top');
76                p2 = this.getPoint(i, 'bottom');
77                break;
78            }
79        }
80        // Exit if no scroll points where found
81        if (match === undefined) {
82            return null;
83        }
84
85
86        // Build bottom scroll point if needed
87        if (p1 === undefined) {
88            p1 = this.getPoint(i-1, 'bottom');
89            p2 = this.getPoint(i, 'top');
90        }
91
92        // Get the normalized scroll progress between both scrollpoints on the editor side
93        const normalized = (leftScroll - p1.left) / (p2.left - p1.left);
94
95        // Compute the corresponding scroll on the preview side
96        const right = p1.right + normalized * (p2.right - p1.right);
97
98        // Debug line
99        const scroll = {p1:p1, p2:p2, right:right};
100        this.debugLine(scroll);
101
102        // Return
103        return scroll;
104    }
105
106    getPoint(i, side) {
107        var type, left, right;
108        var linenum = side.toUpperCase();
109        // Build a TOP scrollpoint if needed
110        if (i < 0) {
111            type = 'TOP';
112            left = 0;
113            right = 0;
114        // Build a BOTTOM scrollpoint if needed
115        } else if (i >= moaiEditor.matches.matches.length) {
116            type = 'BOTTOM';
117            left = moaiEditor.editor.current.scroll.max;
118            right = this.container.scrollHeight;
119        // Get the scrollpoint from the match
120        } else {
121            const match = moaiEditor.matches.matches[i];
122            type  = match.type,
123            left  = match.scroll[side],
124            right = match.handle.getBoundingClientRect()[side] - this.containerTop + this.container.scrollTop;
125            linenum = match.startline;
126            if (side == 'bottom')
127                linenum = match.endline;
128        }
129        // Pack and return
130        return {linenum:linenum, type:type, left:left, right:right, side:side};
131    }
132
133    debugLine(scroll) {
134        // Exit if debugline is not enabled
135        const line = document.querySelector("#moai__debug div:nth-child(2)");
136        if (!line) return;
137
138        // Debug line
139        const p1 = scroll.p1;
140        const p2 = scroll.p2;
141        const txt1 = "<"+scroll.p1.type+"> "+p1.side+" L"+p1.linenum;
142        const txt2 = "<"+scroll.p2.type+"> "+p2.side+" L"+p2.linenum;
143        const text = txt1+" ⇔ "+txt2;
144        line.textContent = text;
145    }
146}; // End Class
147
148// ▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆
149// ▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆▆
150
151MoaiEditor.Scroller = class {
152
153    constructor(outer) {
154        this.outer = outer;
155        this.i = 0;
156        this.target = null;
157        this._engaged = false;
158        this.onFrame();
159    }
160    get engaged() {
161        return this._engaged;
162    }
163    set engaged(boolean) {
164        this._engaged = boolean;
165        this.lastScroll = moaiEditor.preview.scroll.top;
166    }
167    onFrame() {
168        requestAnimationFrame(this.onFrame.bind(this));
169        //this.i += 1; if (this.i % 3 !== 0) return;      // Test other framerates
170        const dt = Date.now() - this.lastTime;
171        this.lastTime = Date.now();
172        if (!this.engaged  ||  !this.outer.isEnabled  ||  this.target === null)
173            return;
174        // Disengage if the user scrolled the preview
175        const scroll = moaiEditor.preview.scroll.top;
176        const userScrolled = Math.abs(scroll - this.lastScroll) > 2;
177        if (userScrolled) {
178            this.engaged = false;
179            return;
180        }
181        // Scroll the preview
182        const distance = this.target - scroll;
183        const increment = 1*distance*dt/160;
184        moaiEditor.preview.scroll.top = scroll + increment;
185        this.lastScroll = scroll + increment;
186    }
187}; // End Class
188
189
190