diff options
author | Hsieh Chin Fan <pham@topo.tw> | 2024-10-02 10:12:46 +0800 |
---|---|---|
committer | Hsieh Chin Fan <pham@topo.tw> | 2024-10-02 10:12:46 +0800 |
commit | 1c7e77b8546ac32b8176ab54dd06ede6ba7deb55 (patch) | |
tree | ca0d50e7b8b3dcbd2cc8891786dca32f1face931 /src/Layout.mjs | |
parent | b7e898bc9022a6d7caef705ee2764c139ec86299 (diff) |
style: switch to standardjs
Diffstat (limited to 'src/Layout.mjs')
-rw-r--r-- | src/Layout.mjs | 231 |
1 files changed, 116 insertions, 115 deletions
diff --git a/src/Layout.mjs b/src/Layout.mjs index 3a835c9..89a48ae 100644 --- a/src/Layout.mjs +++ b/src/Layout.mjs | |||
@@ -1,208 +1,209 @@ | |||
1 | import PlainDraggable from 'plain-draggable'; | 1 | import PlainDraggable from 'plain-draggable' |
2 | import { onRemove, animateRectTransition } from './utils'; | 2 | import { onRemove, animateRectTransition } from './utils' |
3 | 3 | ||
4 | export class Layout { | 4 | export class Layout { |
5 | constructor(options = {}) { | 5 | constructor (options = {}) { |
6 | if (!options.name) throw Error('Layout name is not given'); | 6 | if (!options.name) throw Error('Layout name is not given') |
7 | this.name = options.name; | 7 | this.name = options.name |
8 | this.enterHandler = options.enterHandler; | 8 | this.enterHandler = options.enterHandler |
9 | this.leaveHandler = options.leaveHandler; | 9 | this.leaveHandler = options.leaveHandler |
10 | } | 10 | } |
11 | valueOf = () => this.name; | 11 | |
12 | valueOf = () => this.name | ||
12 | } | 13 | } |
13 | 14 | ||
14 | export class SideBySide extends Layout { | 15 | export class SideBySide extends Layout { |
15 | name = 'side-by-side'; | 16 | name = 'side-by-side' |
16 | 17 | ||
17 | enterHandler = ({ container, htmlHolder, showcase }) => { | 18 | enterHandler = ({ container, htmlHolder, showcase }) => { |
18 | const bar = document.createElement('div'); | 19 | const bar = document.createElement('div') |
19 | bar.className = 'bar'; | 20 | bar.className = 'bar' |
20 | bar.innerHTML = '<div class="bar-handle"></div>'; | 21 | bar.innerHTML = '<div class="bar-handle"></div>' |
21 | const handle = bar.querySelector('.bar-handle'); | 22 | const handle = bar.querySelector('.bar-handle') |
22 | container.appendChild(bar); | 23 | container.appendChild(bar) |
23 | 24 | ||
24 | // Resize views by value | 25 | // Resize views by value |
25 | const resizeByLeft = left => { | 26 | const resizeByLeft = left => { |
26 | htmlHolder.style.width = left + 'px'; | 27 | htmlHolder.style.width = left + 'px' |
27 | showcase.style.width = | 28 | showcase.style.width = |
28 | parseFloat(getComputedStyle(container).width) - left + 'px'; | 29 | parseFloat(window.getComputedStyle(container).width) - left + 'px' |
29 | }; | 30 | } |
30 | 31 | ||
31 | const draggable = new PlainDraggable(bar, { | 32 | const draggable = new PlainDraggable(bar, { |
32 | handle: handle, | 33 | handle, |
33 | containment: { left: '25%', top: 0, right: '75%', height: 0 }, | 34 | containment: { left: '25%', top: 0, right: '75%', height: 0 } |
34 | }); | 35 | }) |
35 | draggable.draggableCursor = 'grab'; | 36 | draggable.draggableCursor = 'grab' |
36 | 37 | ||
37 | draggable.onDrag = pos => { | 38 | draggable.onDrag = pos => { |
38 | handle.style.transform = 'unset'; | 39 | handle.style.transform = 'unset' |
39 | resizeByLeft(pos.left); | 40 | resizeByLeft(pos.left) |
40 | }; | 41 | } |
41 | draggable.onDragEnd = _ => { | 42 | draggable.onDragEnd = _ => { |
42 | handle.removeAttribute('style'); | 43 | handle.removeAttribute('style') |
43 | }; | 44 | } |
44 | 45 | ||
45 | onRemove(bar, () => draggable.remove()); | 46 | onRemove(bar, () => draggable.remove()) |
46 | }; | 47 | } |
47 | 48 | ||
48 | leaveHandler = ({ container }) => { | 49 | leaveHandler = ({ container }) => { |
49 | container.querySelector('.bar')?.remove(); | 50 | container.querySelector('.bar')?.remove() |
50 | }; | 51 | } |
51 | } | 52 | } |
52 | 53 | ||
53 | export class Overlay extends Layout { | 54 | export class Overlay extends Layout { |
54 | name = 'overlay'; | 55 | name = 'overlay' |
55 | 56 | ||
56 | saveLeftTopAsData = element => { | 57 | saveLeftTopAsData = element => { |
57 | const { left, top } = element.getBoundingClientRect(); | 58 | const { left, top } = element.getBoundingClientRect() |
58 | element.setAttribute('data-left', left); | 59 | element.setAttribute('data-left', left) |
59 | element.setAttribute('data-top', top); | 60 | element.setAttribute('data-top', top) |
60 | }; | 61 | } |
61 | 62 | ||
62 | addDraggable = element => { | 63 | addDraggable = element => { |
63 | // Make sure current element always on top | 64 | // Make sure current element always on top |
64 | const siblings = Array.from( | 65 | const siblings = Array.from( |
65 | element.parentElement?.querySelectorAll(':scope > *') ?? [], | 66 | element.parentElement?.querySelectorAll(':scope > *') ?? [] |
66 | ); | 67 | ) |
67 | let popTimer = null; | 68 | let popTimer = null |
68 | element.onmouseover = () => { | 69 | element.onmouseover = () => { |
69 | popTimer = setTimeout(() => { | 70 | popTimer = setTimeout(() => { |
70 | siblings.forEach(e => e.style.removeProperty('z-index')); | 71 | siblings.forEach(e => e.style.removeProperty('z-index')) |
71 | element.style.zIndex = '9001'; | 72 | element.style.zIndex = '9001' |
72 | }, 200); | 73 | }, 200) |
73 | }; | 74 | } |
74 | element.onmouseout = () => { | 75 | element.onmouseout = () => { |
75 | clearTimeout(popTimer); | 76 | clearTimeout(popTimer) |
76 | }; | 77 | } |
77 | 78 | ||
78 | // Add draggable part | 79 | // Add draggable part |
79 | const draggablePart = document.createElement('div'); | 80 | const draggablePart = document.createElement('div') |
80 | element.appendChild(draggablePart); | 81 | element.appendChild(draggablePart) |
81 | draggablePart.className = 'draggable-part'; | 82 | draggablePart.className = 'draggable-part' |
82 | draggablePart.innerHTML = '<div class="handle">\u2630</div>'; | 83 | draggablePart.innerHTML = '<div class="handle">\u2630</div>' |
83 | 84 | ||
84 | // Add draggable instance | 85 | // Add draggable instance |
85 | const { left, top } = element.getBoundingClientRect(); | 86 | const { left, top } = element.getBoundingClientRect() |
86 | const draggable = new PlainDraggable(element, { | 87 | const draggable = new PlainDraggable(element, { |
87 | top: top, | 88 | top, |
88 | left: left, | 89 | left, |
89 | handle: draggablePart, | 90 | handle: draggablePart, |
90 | snap: { x: { step: 20 }, y: { step: 20 } }, | 91 | snap: { x: { step: 20 }, y: { step: 20 } } |
91 | }); | 92 | }) |
92 | 93 | ||
93 | // FIXME use pure CSS to hide utils | 94 | // FIXME use pure CSS to hide utils |
94 | const utils = element.querySelector('.utils'); | 95 | const utils = element.querySelector('.utils') |
95 | draggable.onDragStart = () => { | 96 | draggable.onDragStart = () => { |
96 | utils.style.display = 'none'; | 97 | utils.style.display = 'none' |
97 | element.classList.add('drag'); | 98 | element.classList.add('drag') |
98 | }; | 99 | } |
99 | 100 | ||
100 | draggable.onDragEnd = () => { | 101 | draggable.onDragEnd = () => { |
101 | utils.style = ''; | 102 | utils.style = '' |
102 | element.classList.remove('drag'); | 103 | element.classList.remove('drag') |
103 | element.style.zIndex = '9000'; | 104 | element.style.zIndex = '9000' |
104 | }; | 105 | } |
105 | 106 | ||
106 | // Reposition draggable instance when resized | 107 | // Reposition draggable instance when resized |
107 | new ResizeObserver(() => { | 108 | new window.ResizeObserver(() => { |
108 | try { | 109 | try { |
109 | draggable.position(); | 110 | draggable.position() |
110 | } catch (_) { | 111 | } catch (err) { |
111 | null; | 112 | console.warn(err) |
112 | } | 113 | } |
113 | }).observe(element); | 114 | }).observe(element) |
114 | 115 | ||
115 | // Callback for remove | 116 | // Callback for remove |
116 | onRemove(element, () => { | 117 | onRemove(element, () => { |
117 | draggable.remove(); | 118 | draggable.remove() |
118 | }); | 119 | }) |
119 | }; | 120 | } |
120 | 121 | ||
121 | enterHandler = ({ htmlHolder, blocks }) => { | 122 | enterHandler = ({ htmlHolder, blocks }) => { |
122 | // FIXME It is weird rect from this method and this scope are different... | 123 | // FIXME It is weird rect from this method and this scope are different... |
123 | blocks.forEach(this.saveLeftTopAsData); | 124 | blocks.forEach(this.saveLeftTopAsData) |
124 | 125 | ||
125 | // Create draggable blocks and set each position by previous one | 126 | // Create draggable blocks and set each position by previous one |
126 | let [left, top] = [20, 20]; | 127 | let [left, top] = [20, 20] |
127 | blocks.forEach(block => { | 128 | blocks.forEach(block => { |
128 | const originLeft = Number(block.getAttribute('data-left')); | 129 | const originLeft = Number(block.getAttribute('data-left')) |
129 | const originTop = Number(block.getAttribute('data-top')); | 130 | const originTop = Number(block.getAttribute('data-top')) |
130 | 131 | ||
131 | // Create draggable block | 132 | // Create draggable block |
132 | const wrapper = document.createElement('div'); | 133 | const wrapper = document.createElement('div') |
133 | wrapper.classList.add('draggable-block'); | 134 | wrapper.classList.add('draggable-block') |
134 | wrapper.innerHTML = ` | 135 | wrapper.innerHTML = ` |
135 | <div class="utils"> | 136 | <div class="utils"> |
136 | <div id="close">\u274C</div> | 137 | <div id="close">\u274C</div> |
137 | <div id="plus-font-size" ">\u2795</div> | 138 | <div id="plus-font-size" ">\u2795</div> |
138 | <div id="minus-font-size">\u2796</div> | 139 | <div id="minus-font-size">\u2796</div> |
139 | </div> | 140 | </div> |
140 | `; | 141 | ` |
141 | wrapper.title = 'Middle-click to hide block'; | 142 | wrapper.title = 'Middle-click to hide block' |
142 | wrapper.onmouseup = e => { | 143 | wrapper.onmouseup = e => { |
143 | // Hide block with middle click | 144 | // Hide block with middle click |
144 | if (e.button === 1) { | 145 | if (e.button === 1) { |
145 | wrapper.classList.add('hide'); | 146 | wrapper.classList.add('hide') |
146 | } | 147 | } |
147 | }; | 148 | } |
148 | 149 | ||
149 | // Set DOMRect for wrapper | 150 | // Set DOMRect for wrapper |
150 | wrapper.appendChild(block); | 151 | wrapper.appendChild(block) |
151 | wrapper.style.left = left + 'px'; | 152 | wrapper.style.left = left + 'px' |
152 | wrapper.style.top = top + 'px'; | 153 | wrapper.style.top = top + 'px' |
153 | htmlHolder.appendChild(wrapper); | 154 | htmlHolder.appendChild(wrapper) |
154 | const { width } = wrapper.getBoundingClientRect(); | 155 | const { width } = wrapper.getBoundingClientRect() |
155 | left += width + 30; | 156 | left += width + 30 |
156 | if (left > window.innerWidth) { | 157 | if (left > window.innerWidth) { |
157 | top += 200; | 158 | top += 200 |
158 | left = left % window.innerWidth; | 159 | left = left % window.innerWidth |
159 | } | 160 | } |
160 | 161 | ||
161 | // Animation for DOMRect | 162 | // Animation for DOMRect |
162 | animateRectTransition( | 163 | animateRectTransition( |
163 | wrapper, | 164 | wrapper, |
164 | { left: originLeft, top: originTop }, | 165 | { left: originLeft, top: originTop }, |
165 | { resume: true, duration: 300 }, | 166 | { resume: true, duration: 300 } |
166 | ).finished.finally(() => this.addDraggable(wrapper)); | 167 | ).finished.finally(() => this.addDraggable(wrapper)) |
167 | 168 | ||
168 | // Trivial case: | 169 | // Trivial case: |
169 | // This hack make sure utils remains at the same place even when wrapper resized | 170 | // This hack make sure utils remains at the same place even when wrapper resized |
170 | // Prevent DOMRect changes when user clicking plus/minus button many times | 171 | // Prevent DOMRect changes when user clicking plus/minus button many times |
171 | const utils = wrapper.querySelector('.utils'); | 172 | const utils = wrapper.querySelector('.utils') |
172 | utils.onmouseover = () => { | 173 | utils.onmouseover = () => { |
173 | const { left, top } = utils.getBoundingClientRect(); | 174 | const { left, top } = utils.getBoundingClientRect() |
174 | utils.style.cssText = `visibility: visible; z-index: 9000; position: fixed; transition: unset; left: ${left}px; top: ${top}px;`; | 175 | utils.style.cssText = `visibility: visible; z-index: 9000; position: fixed; transition: unset; left: ${left}px; top: ${top}px;` |
175 | document.body.appendChild(utils); | 176 | document.body.appendChild(utils) |
176 | }; | 177 | } |
177 | utils.onmouseout = () => { | 178 | utils.onmouseout = () => { |
178 | wrapper.appendChild(utils); | 179 | wrapper.appendChild(utils) |
179 | utils.removeAttribute('style'); | 180 | utils.removeAttribute('style') |
180 | }; | 181 | } |
181 | 182 | ||
182 | // Close button | 183 | // Close button |
183 | wrapper.querySelector('#close').onclick = () => { | 184 | wrapper.querySelector('#close').onclick = () => { |
184 | wrapper.classList.add('hide'); | 185 | wrapper.classList.add('hide') |
185 | utils.removeAttribute('style'); | 186 | utils.removeAttribute('style') |
186 | }; | 187 | } |
187 | // Plus/Minus font-size of content | 188 | // Plus/Minus font-size of content |
188 | wrapper.querySelector('#plus-font-size').onclick = () => { | 189 | wrapper.querySelector('#plus-font-size').onclick = () => { |
189 | const fontSize = parseFloat(getComputedStyle(block).fontSize) / 16; | 190 | const fontSize = parseFloat(window.getComputedStyle(block).fontSize) / 16 |
190 | block.style.fontSize = `${fontSize + 0.2}rem`; | 191 | block.style.fontSize = `${fontSize + 0.2}rem` |
191 | }; | 192 | } |
192 | wrapper.querySelector('#minus-font-size').onclick = () => { | 193 | wrapper.querySelector('#minus-font-size').onclick = () => { |
193 | const fontSize = parseFloat(getComputedStyle(block).fontSize) / 16; | 194 | const fontSize = parseFloat(window.getComputedStyle(block).fontSize) / 16 |
194 | block.style.fontSize = `${fontSize - 0.2}rem`; | 195 | block.style.fontSize = `${fontSize - 0.2}rem` |
195 | }; | 196 | } |
196 | }); | 197 | }) |
197 | }; | 198 | } |
198 | 199 | ||
199 | leaveHandler = ({ htmlHolder, blocks }) => { | 200 | leaveHandler = ({ htmlHolder, blocks }) => { |
200 | const resumeFromDraggable = block => { | 201 | const resumeFromDraggable = block => { |
201 | const draggableContainer = block.closest('.draggable-block'); | 202 | const draggableContainer = block.closest('.draggable-block') |
202 | if (!draggableContainer) return; | 203 | if (!draggableContainer) return |
203 | htmlHolder.appendChild(block); | 204 | htmlHolder.appendChild(block) |
204 | draggableContainer.remove(); | 205 | draggableContainer.remove() |
205 | }; | 206 | } |
206 | blocks.forEach(resumeFromDraggable); | 207 | blocks.forEach(resumeFromDraggable) |
207 | }; | 208 | } |
208 | } | 209 | } |