aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/Layout.mjs
diff options
context:
space:
mode:
authorHsieh Chin Fan <pham@topo.tw>2024-10-02 10:12:46 +0800
committerHsieh Chin Fan <pham@topo.tw>2024-10-02 10:12:46 +0800
commit1c7e77b8546ac32b8176ab54dd06ede6ba7deb55 (patch)
treeca0d50e7b8b3dcbd2cc8891786dca32f1face931 /src/Layout.mjs
parentb7e898bc9022a6d7caef705ee2764c139ec86299 (diff)
style: switch to standardjs
Diffstat (limited to 'src/Layout.mjs')
-rw-r--r--src/Layout.mjs231
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 @@
1import PlainDraggable from 'plain-draggable'; 1import PlainDraggable from 'plain-draggable'
2import { onRemove, animateRectTransition } from './utils'; 2import { onRemove, animateRectTransition } from './utils'
3 3
4export class Layout { 4export 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
14export class SideBySide extends Layout { 15export 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
53export class Overlay extends Layout { 54export 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}