Skip to content

Commit bb65179

Browse files
committed
setup rotatable Rotation (to fix GoogleChromeLabs#613 - try number 2 after PR GoogleChromeLabs#657)
1 parent 382ecff commit bb65179

File tree

8 files changed

+149
-4
lines changed

8 files changed

+149
-4
lines changed

app/components/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export { Overlay } from './selection/overlay.element'
88
export { BoxModel } from './selection/box-model.element'
99
export { Corners } from './selection/corners.element'
1010
export { Grip } from './selection/grip.element'
11+
export { Rotation } from './selection/rotation.element'
1112

1213
export { Metatip } from './metatip/metatip.element'
1314
export { Ally } from './metatip/ally.element'
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
@import "../_variables.css";
2+
3+
:host {
4+
position: var(--position);
5+
top: var(--top);
6+
left: var(--left);
7+
pointer-events: none;
8+
}
9+
10+
:host .rotation-handle {
11+
position: absolute;
12+
width: 24px;
13+
height: 24px;
14+
top: -30px;
15+
left: calc(var(--width) / 2 - 12px);
16+
cursor: grab;
17+
pointer-events: all;
18+
background: var(--theme-color);
19+
border-radius: 50%;
20+
display: flex;
21+
align-items: center;
22+
justify-content: center;
23+
}
24+
25+
:host .rotation-handle:active {
26+
cursor: grabbing;
27+
}
28+
29+
:host .rotation-icon {
30+
width: 16px;
31+
height: 16px;
32+
fill: white;
33+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { HandlesStyles, RotationStyles } from '../styles.store'
2+
3+
export class Rotation extends HTMLElement {
4+
constructor() {
5+
super()
6+
this.$shadow = this.attachShadow({mode: 'closed'})
7+
this.styles = [HandlesStyles, RotationStyles]
8+
this.startAngle = 0
9+
this.currentAngle = 0
10+
}
11+
12+
connectedCallback() {
13+
this.$shadow.adoptedStyleSheets = this.styles
14+
}
15+
16+
set position({el}) {
17+
this.targetElement = el
18+
const {left, top, width, height} = el.getBoundingClientRect()
19+
const isFixed = getComputedStyle(el).position === 'fixed'
20+
21+
this.style.setProperty('--top', `${top + (isFixed ? 0 : window.scrollY)}px`)
22+
this.style.setProperty('--left', `${left}px`)
23+
this.style.setProperty('--position', isFixed ? 'fixed' : 'absolute')
24+
this.style.setProperty('--width', `${width}px`)
25+
26+
this.$shadow.innerHTML = this.render()
27+
this.setupRotationHandlers()
28+
}
29+
30+
setupRotationHandlers() {
31+
const handle = this.$shadow.querySelector('.rotation-handle')
32+
33+
const onMouseDown = e => {
34+
e.preventDefault()
35+
const {left, top, width, height} = this.targetElement.getBoundingClientRect()
36+
const center = {
37+
x: left + width / 2,
38+
y: top + height / 2
39+
}
40+
this.startAngle = Math.atan2(
41+
e.clientY - center.y,
42+
e.clientX - center.x
43+
)
44+
45+
document.addEventListener('mousemove', onMouseMove)
46+
document.addEventListener('mouseup', onMouseUp)
47+
}
48+
49+
const onMouseMove = e => {
50+
const {left, top, width, height} = this.targetElement.getBoundingClientRect()
51+
const center = {
52+
x: left + width / 2,
53+
y: top + height / 2
54+
}
55+
56+
const angle = Math.atan2(
57+
e.clientY - center.y,
58+
e.clientX - center.x
59+
)
60+
61+
const rotation = angle - this.startAngle
62+
this.currentAngle += rotation
63+
this.startAngle = angle
64+
65+
this.targetElement.style.transform = `rotate(${this.currentAngle * (180 / Math.PI)}deg)`
66+
}
67+
68+
const onMouseUp = () => {
69+
document.removeEventListener('mousemove', onMouseMove)
70+
document.removeEventListener('mouseup', onMouseUp)
71+
}
72+
73+
handle.addEventListener('mousedown', onMouseDown)
74+
75+
this.cleanup = () => {
76+
handle.removeEventListener('mousedown', onMouseDown)
77+
}
78+
}
79+
80+
disconnectedCallback() {
81+
this.cleanup && this.cleanup()
82+
}
83+
84+
render() {
85+
return `
86+
<div class="rotation-handle">
87+
<svg class="rotation-icon" viewBox="0 0 24 24">
88+
<path d="M12 5V1L7 6l5 5V7c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6H4c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z"/>
89+
</svg>
90+
</div>
91+
`
92+
}
93+
}
94+
95+
customElements.define('visbug-rotation', Rotation)

app/components/styles.store.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { default as boxmodel_css } from './selection/box-model.element.css'
1414
import { default as metatip_css } from './metatip/metatip.element.css'
1515
import { default as hotkeymap_css } from './hotkey-map/base.element.css'
1616
import { default as grip_css } from './selection/grip.element.css'
17+
import { default as rotation_css } from './selection/rotation.element.css'
1718

1819
import { default as light_css } from './_variables_light.css'
1920
import { default as visbug_light_css } from './vis-bug/vis-bug.element_light.css'
@@ -44,6 +45,7 @@ export const OverlayStyles = constructStylesheet(overlay_css)
4445
export const BoxModelStyles = constructStylesheet(boxmodel_css)
4546
export const HotkeymapStyles = constructStylesheet(hotkeymap_css)
4647
export const GripStyles = constructStylesheet(grip_css)
48+
export const RotationStyles = constructStylesheet(rotation_css)
4749

4850
export const LightTheme = constructStylesheet(light_css)
4951
export const VisBugLightStyles = constructStylesheet(visbug_light_css)

app/components/vis-bug/vis-bug.element.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import hotkeys from 'hotkeys-js'
33

44
import {
55
Handles, Handle, Label, Overlay, Gridlines, Corners,
6-
Hotkeys, Metatip, Ally, Distance, BoxModel, Grip
6+
Hotkeys, Metatip, Ally, Distance, BoxModel, Grip, Rotation
77
} from '../'
88

99
import {

app/features/position.js

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,11 @@ export function Position() {
2727
state.elements.forEach(el =>
2828
el.teardown())
2929

30-
state.elements = els.map(el =>
31-
draggable({el}))
30+
state.elements = els.map(el => {
31+
draggable({el})
32+
rotatable({el})
33+
return el
34+
})
3235
}
3336

3437
const disconnect = () => {
@@ -162,6 +165,16 @@ export function draggable({el, surface = el, cursor = 'move', clickEvent}) {
162165
return el
163166
}
164167

168+
export function rotatable({el}) {
169+
const rotation = document.createElement('visbug-rotation')
170+
document.body.appendChild(rotation)
171+
rotation.position = {el}
172+
173+
el.teardown = () => rotation.remove()
174+
175+
return el
176+
}
177+
165178
export function positionElement(els, direction) {
166179
els
167180
.map(el => ensurePositionable(el))

app/utilities/common.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ export const isOffBounds = node =>
8989
|| node.closest('visbug-corners')
9090
|| node.closest('visbug-grip')
9191
|| node.closest('visbug-gridlines')
92+
|| node.closest('visbug-rotation')
9293
)
9394

9495
export const isSelectorValid = (qs => (

app/utilities/strings.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,4 @@ export const altKey = window.navigator.platform.includes('Mac')
4444
? 'opt'
4545
: 'alt'
4646

47-
export const notList = ':not(vis-bug):not(script):not(hotkey-map):not(.visbug-metatip):not(visbug-label):not(visbug-handles):not(visbug-corners):not(visbug-grip):not(visbug-gridlines)'
47+
export const notList = ':not(vis-bug):not(script):not(hotkey-map):not(.visbug-metatip):not(visbug-label):not(visbug-handles):not(visbug-corners):not(visbug-grip):not(visbug-gridlines):not(visbug-rotation)'

0 commit comments

Comments
 (0)