TASK: #104293 - add function for dragging icon into dock

This commit is contained in:
luojiahao 2022-09-11 15:04:13 +08:00
parent 651a560cdf
commit c7ff88caba
7 changed files with 515 additions and 143 deletions

View File

@ -18,3 +18,4 @@
- add delay exchange feature
- fix bugs of container exchange stradegy
- add dock
- add function for dragging icon into dock

View File

@ -1,28 +1,114 @@
import {html, css, LitElement, CSSResultGroup} from 'lit'
import {customElement, query} from 'lit/decorators.js'
import {customElement, query, state} from 'lit/decorators.js'
import DockChild from './dock-child'
import customStyle from './style'
interface DragAndDrop {
// Whether drag-and-drop is enabled
enabled: boolean
// The time, in ms, to wait before initiating a drag-and-drop from a
// long-press
delay: number
lastMoveEventTime: number
// Timeout used to initiate drag-and-drop actions
timeout: number | undefined
// The child that was tapped/clicked
child: any
// Whether a drag is active
active: boolean
// The start point of the drag action
start: {
pageX: number
pageY: number
clientX: number
clientY: number
translateX?: number
translateY?: number
}
// The last point of the drag action
last: {
pageX: number
pageY: number
clientX: number
clientY: number
timeStamp: number
offsetHeight: number
offsetWidth: number
offsetX: number
offsetY: number
}
// Timeout used to send drag-move events
moveTimeout: number | undefined
top: number
left: number
// 中心坐标
center: {
x: number
y: number
}
}
const DND_MOVE_THROTTLE = 50
const DND_THRESHOLD = 5
const DEFAULT_DND_TIMEOUT = 300
@customElement('star-dock')
export default class StarDock extends LitElement {
@query('#container') container!: HTMLElement
_children: DockChild[] = []
_dnd: {[prop: string]: any} = {}
_sortMode: Boolean = false
_dragInElement: HTMLElement | undefined
_temporaryChild: DockChild | undefined
_dragChild: DockChild | undefined
_gridSize!: number
distance!: number
_containerOffset: {[position: string]: number} = {
top: 0,
left: 0,
}
_dnd: DragAndDrop = {
enabled: true,
active: false,
child: undefined,
timeout: undefined,
moveTimeout: undefined,
lastMoveEventTime: -1,
delay: DEFAULT_DND_TIMEOUT,
top: 0,
left: 0,
constructor() {
super()
this.addEventListener('touchstart', this)
this.addEventListener('touchmove', this)
this.addEventListener('touchend', this)
this.addEventListener('touchcancel', this)
center: {
x: 0,
y: 0,
},
last: {
pageX: 0,
pageY: 0,
clientX: 0,
clientY: 0,
timeStamp: 0,
offsetHeight: 0,
offsetWidth: 0,
offsetX: 0,
offsetY: 0,
},
start: {
pageX: 0,
pageY: 0,
clientX: 0,
clientY: 0,
translateX: 0,
translateY: 0,
},
}
appendContainerChild = (element: HTMLElement, options?: number) => {
@ -37,6 +123,7 @@ export default class StarDock extends LitElement {
if (!target) return null
this._children = this._children.filter((child) => child !== target)
target.master.remove()
return target
}
realRemoveChild = (element: HTMLElement) => {}
@ -97,54 +184,214 @@ export default class StarDock extends LitElement {
return undefined
}
handleEvent(evt: TouchEvent) {}
dropPosition: 'outter' | 'inner' = 'inner'
handleEvent(event: TouchEvent) {
switch (event.type) {
case 'touchstart':
case 'click':
case 'mousedown':
if (this._dnd.active || this._dnd.timeout) {
break
}
dragIn = (info: any) => {
if (event instanceof MouseEvent) {
this._dnd.start.pageX = event.pageX
this._dnd.start.pageY = event.pageY
this._dnd.start.clientX = event.clientX
this._dnd.start.clientY = event.clientY
} else if (event instanceof TouchEvent) {
this._dnd.start.pageX = event.touches[0].pageX
this._dnd.start.pageY = event.touches[0].pageY
this._dnd.start.clientX = event.touches[0].clientX
this._dnd.start.clientY = event.touches[0].clientY
}
this._dnd.last.pageX = this._dnd.start.pageX
this._dnd.last.pageY = this._dnd.start.pageY
this._dnd.last.clientX = this._dnd.start.clientX
this._dnd.last.clientY = this._dnd.start.clientY
this._dnd.last.timeStamp = event.timeStamp
let target = event.target as HTMLElement //gaia-app-icon
// Find the child
let children = [...this._children]
for (let child of children) {
if (child.element === target || child.master === target) {
this._dnd.child = child
break
}
}
if (!this._dnd.child) {
return
}
if (this._dnd.delay > 0) {
this._dnd.timeout = setTimeout(() => {
this._dnd.timeout = undefined
this.startDrag()
}, this._dnd.delay)
} else {
this.startDrag()
}
break
case 'touchmove':
case 'mousemove':
let pageX: number, pageY: number, clientX, clientY
if (event instanceof MouseEvent) {
pageX = event.pageX
pageY = event.pageY
clientX = event.clientX
clientY = event.clientY
} else {
pageX = (event as TouchEvent).touches[0].pageX
pageY = (event as TouchEvent).touches[0].pageY
clientX = (event as TouchEvent).touches[0].clientX
clientY = (event as TouchEvent).touches[0].clientY
}
this.distance = pageX - this._dnd.last.pageX
if (this._dnd.timeout) {
if (
Math.abs(pageX - this._dnd.start.pageX) > DND_THRESHOLD ||
Math.abs(pageY - this._dnd.start.pageY) > DND_THRESHOLD
) {
clearTimeout(this._dnd.timeout)
this._dnd.timeout = undefined
}
} else if (this._dnd.active) {
event.preventDefault()
this._dnd.last.pageX = pageX
this._dnd.last.pageY = pageY
this._dnd.last.clientX = clientX
this._dnd.last.clientY = clientY
this._dnd.last.timeStamp = event.timeStamp
this.continueDrag()
}
if (this._dnd.active) {
const centerX =
this._dnd.center.x + (this._dnd.last.pageX - this._dnd.start.pageX)
const centerY =
this._dnd.center.y + (this._dnd.last.pageY - this._dnd.start.pageY)
if (this.dropPosition == 'inner' && centerY < this.offsetTop) {
this.dropPosition = 'outter'
this.dispatchEvent(
new CustomEvent('out-of-dock', {
detail: {
target: this._dnd.child.element,
centerX,
centerY,
},
composed: true,
})
)
} else if (
this.dropPosition == 'outter' &&
centerY > this.offsetTop
) {
this.dropPosition = 'inner'
this.dispatchEvent(
new CustomEvent('drag-return-dock', {
detail: this._dnd.child.element,
composed: true,
})
)
}
}
break
case 'touchcancel':
this.cancelDrag()
break
case 'touchend':
case 'mouseup':
if (this._dnd.active) {
event.preventDefault()
}
this.endDrag(event)
break
case 'contextmenu':
if (this._dnd.active || this._dnd.timeout) {
event.stopImmediatePropagation()
}
break
}
}
get sortMode() {
return this._sortMode
}
set sortMode(value) {
if (value) {
this._dnd.delay = 0
} else {
this._dnd.delay = DEFAULT_DND_TIMEOUT
}
this._sortMode = value
}
/**
* dock dock
* @param info
* @returns
*/
dragIn = (info: {
element: HTMLElement | DockChild
centerX: number
centerY: number
}) => {
const {element, centerX, centerY} = info
const dropChild = this.getChildFromPoint(centerX, centerY)
let child = this.getChildByElement(element)
let child =
element instanceof DockChild ? element : this.getChildByElement(element)
if (!child) {
this._dragInElement = element
this._temporaryChild = new DockChild(element, this)
this._children.push(this._temporaryChild)
console.log([...this._children])
this._dragInElement = element as HTMLElement
this._dragChild = new DockChild(element as HTMLElement, this)
this._dragChild.container.classList.add('dragging')
this._children.push(this._dragChild)
child = this._temporaryChild
child = this._dragChild
}
child.container.style.transform =
'translate(' +
(centerX - this._gridSize / 2) +
(centerX - (child.master.offsetWidth || this._gridSize) / 2) +
'px, ' +
(centerY - this.getBoundingClientRect().top - this._gridSize / 2) +
(centerY - (child.master.offsetHeight || this._gridSize) / 2) +
'px)'
if (!dropChild) {
return false
}
if (dropChild == this._temporaryChild) {
console.log('abc')
if (dropChild == this._dragChild) {
} else if (dropChild instanceof DockChild) {
if (centerX < dropChild.center.x) {
this.realInsertBefore(this._temporaryChild?.master!, dropChild.master)
this.realInsertBefore(this._dragChild?.master!, dropChild.master)
} else {
this.realInsertAfter(this._temporaryChild?.master!, dropChild.master)
this.realInsertAfter(this._dragChild?.master!, dropChild.master)
}
} else if (dropChild === this.container) {
if (this._children.length && this._children[0].center.x > centerX) {
if (this.container.firstElementChild == this._temporaryChild!.master)
if (this.container.firstElementChild == this._dragChild!.master)
return true
this.realInsertBefore(
this._temporaryChild?.master!,
this._dragChild?.master!,
this._children[0].master
)
} else {
if (this.container.lastElementChild == this._temporaryChild!.master)
if (this.container.lastElementChild == this._dragChild!.master)
return true
this.container.appendChild(this._temporaryChild?.master!)
this.container.appendChild(this._dragChild?.master!)
}
}
@ -152,11 +399,16 @@ export default class StarDock extends LitElement {
return true
}
/**
*
* @param element element
* @returns
*/
dragOut = (element: HTMLElement) => {
let child: DockChild | undefined
if (this._dragInElement == element) {
child = this._temporaryChild
this._temporaryChild = undefined
child = this._dragChild
this._dragChild = undefined
this._dragInElement = undefined
} else {
child = this.getChildByElement(element)
@ -165,6 +417,7 @@ export default class StarDock extends LitElement {
if (child) {
child.master.remove()
this._children = this._children.filter((item) => item !== child)
this.synchronise()
return true
}
@ -172,10 +425,98 @@ export default class StarDock extends LitElement {
return false
}
startDrag = () => {}
continueDrag = () => {}
endDrag = () => {}
cancelDrag = () => {}
startDrag = () => {
this.dataset.dragging = 'true'
const child = this._dnd.child
this._dragChild = this._dnd.child
this._dnd.active = true
this._dragChild?.container.classList.add('dragging')
this._dnd.start.translateX = child.master.offsetLeft
this._dnd.start.translateY = child.master.offsetTop
this._dnd.center.x = child._lastMasterLeft + child.master.offsetWidth / 2
this._dnd.center.y = child._lastMasterTop + child.master.offsetHeight / 2
if (
!this.dispatchEvent(
new CustomEvent('dock-drag-start', {
cancelable: true,
detail: {
target: child.element,
pageX: this._dnd.start.pageX,
pageY: this._dnd.start.pageY,
clientX: this._dnd.start.clientX,
clientY: this._dnd.start.clientY,
},
composed: true,
})
)
) {
return
}
this._dnd.active = true
this._dnd.child.container.classList.add('dragging')
}
continueDrag() {
if (!this._dnd.active) {
return
}
let left =
(this._dnd.start.translateX as number) +
(this._dnd.last.pageX - this._dnd.start.pageX)
let top =
(this._dnd.start.translateY as number) +
(this._dnd.last.pageY - this._dnd.start.pageY)
this._dnd.left = left
this._dnd.top = top
if (this._dnd.moveTimeout === undefined) {
let delay = Math.max(
0,
DND_MOVE_THROTTLE -
(this._dnd.last.timeStamp - this._dnd.lastMoveEventTime)
)
this._dnd.moveTimeout = setTimeout(() => {
this._dnd.moveTimeout = undefined
this._dnd.lastMoveEventTime = this._dnd.last.timeStamp
this.dispatchEvent(
new CustomEvent('dock-drag-move', {
detail: {
target: this._dnd.child.element,
pageX: this._dnd.last.pageX,
pageY: this._dnd.last.pageY,
clientX: this._dnd.last.clientX,
clientY: this._dnd.last.clientY,
},
composed: true,
})
)
}, delay)
}
this.dragIn({
element: this._dnd.child,
centerX:
this._dnd.center.x + (this._dnd.last.pageX - this._dnd.start.pageX),
centerY:
this._dnd.center.y + (this._dnd.last.pageY - this._dnd.start.pageY),
})
}
endDrag = (evt: TouchEvent) => {
this.cancelDrag()
}
cancelDrag = () => {
this.dataset.dragging = 'false'
if (this._dnd.active) {
this._dnd.active = false
this._dnd.child.container.classList.remove('dragging')
}
this.synchronise()
}
/**
*
@ -186,39 +527,35 @@ export default class StarDock extends LitElement {
*
*/
getChildFromPoint = (x: number, y: number) => {
const {top, left} = this.getBoundingClientRect()
let _children = [...this._children]
x -= left
y -= top
let target
if (y > this._containerOffset.top && y < this._containerOffset.top + 140) {
target = this.container
}
for (const child of _children) {
if (
child.master.parentElement && // 此处防止用户操作过快导致图标丢失问题
x > (child._lastMasterLeft ?? child.master.offsetLeft) &&
x < (child._lastMasterLeft ?? child.master.offsetLeft) + this._gridSize
x <
(child._lastMasterLeft ?? child.master.offsetLeft) + this._gridSize
) {
target = child
break
}
}
if (target && target == this._temporaryChild) {
;(window as any).test = {
children: _children,
child: target,
temporary: this._temporaryChild,
left: target._lastMasterLeft ?? target.master.offsetLeft,
}
// debugger
}
return target
}
protected firstUpdated(): void {
this._gridSize = 100
this.container.addEventListener('touchstart', this)
this.container.addEventListener('click', this)
this.container.addEventListener('touchmove', this)
this.container.addEventListener('touchend', this)
this.container.addEventListener('touchcancel', this)
this.resize()
}
@ -236,10 +573,10 @@ export default class StarDock extends LitElement {
customStyle,
css`
:host {
position: relative;
// position: relative;
display: flex;
flex: 1;
margin-bottom: var(--dock-bottom, 60px);
margin-bottom: var(--dock-margin-bottom, 60px);
width: 100%;
height: var(--dock-height, 92px);
}
@ -264,8 +601,14 @@ export default class StarDock extends LitElement {
position: absolute;
top: 0;
left: 0;
z-index: 0;
}
.dock-child-container:not(.dragging) {
transition: transform 0.2s;
}
.dock-child-container.dragging {
z-index: 1;
}
`,
]
}

View File

@ -5,12 +5,12 @@ import {css} from 'lit'
export default css`
:host {
/** dock 与屏幕底部距离 */
--dock-bottom: 30px;
--dock-margin-bottom: var(--dock-bottom, 30px);
/** dock 最小宽度 */
--dock-min-width: 760px;
/** dock 高度 */
--dock-height: 140px;
/** dock 图标宽度 */
--dock-grid-width: var(--icon-size, 92px);
--dock-grid-width: var(--icon-size, 100px);
}
`

View File

@ -4,6 +4,8 @@ export default css`
:host {
position: relative;
display: block;
margin-top: var(--container-margin-top);
margin-left: var(--container-margin-left);
width: 100%;
height: 100%;
@ -35,7 +37,7 @@ export default css`
grid-auto-rows: 33%;
height: 100%;
transform: opacity 0.1s;
width: calc(100% - var(--container-margin-left) * 2);
}
::slotted(.gaia-container-child) {

View File

@ -1,4 +1,4 @@
import {html, css, LitElement} from 'lit'
import {html, LitElement} from 'lit'
import {customElement, property, state} from 'lit/decorators.js'
import GaiaContainerChild from './gaia-container-child'
import GestureManager from './gesture-manager'
@ -92,6 +92,7 @@ class GaiaContainer extends LitElement {
row: number = 6
column: number = 4
_frozen: boolean = false
ready: boolean = false
_pendingStateChanges: Function[] = []
_children: GaiaContainerChild[] = []
_dnd: DragAndDrop = {
@ -214,15 +215,17 @@ class GaiaContainer extends LitElement {
super()
this.row = row
this.column = column
;(window as any).con = this
// this.attachShadow({ mode: "open" });
this.exchangeStratege = new ExchangeStrategy(this)
// this.shadowRoot && (this.shadowRoot.innerHTML = this.template);
}
firstUpdated() {
slotStyleHandler.injectGlobalCss(this, containerStyle.cssText, this.name)
return new Promise((resolve) => {
if (!this.ready) {
slotStyleHandler.injectGlobalCss(
this,
containerStyle.cssText,
this.name
)
let dndObserverCallback = () => {
if (this._dnd.enabled !== this['drag-and-drop']) {
this._dnd.enabled = this['drag-and-drop']
@ -241,9 +244,6 @@ class GaiaContainer extends LitElement {
this.addEventListener('swipeleft', this.swipe)
this.addEventListener('swiperight', this.swipe)
// this.gestureDetector.on('swipeleft', this.swipe);
// this.gestureDetector.on('swiperight', this.swipe);
// this.gestureDetector.on('doubleSwipe', this.swipe);
this.addEventListener('folder-destroy', this.destroyFolder)
// 多指划屏
@ -262,21 +262,22 @@ class GaiaContainer extends LitElement {
this.removeEventListener('swipeleft', this.swipe)
this.removeEventListener('swiperight', this.swipe)
// this.gestureDetector.off('swipeleft', this.swipe);
// this.gestureDetector.off('swiperight', this.swipe);
// this.gestureDetector.off('doubleSwipe', this.swipe);
this.removeEventListener('folder-destroy', this.destroyFolder)
}
}
}
this.dndObserver = new MutationObserver(dndObserverCallback)
// this.dndObserver.observe(this, {
// attributes: true,
// attributeFilter: ['drag-and-drop'],
// })
dndObserverCallback()
this.updateComplete.then(() => {
this.ready = true
this.changeLayout()
resolve(void 0)
})
} else {
resolve(void 0)
}
})
}
_getStatus(status = this._status) {
@ -367,8 +368,7 @@ class GaiaContainer extends LitElement {
const cur = new Date().getTime()
const t = (cur - startTime) / animateTime
const ratio = cubic_bezier(0.2, 1, 0.95, 1, t)
this.offsetX =
origin + parseInt((distance * ratio) as unknown as string)
this.offsetX = origin + parseInt(String(distance * ratio))
// 在切换页面的时候,由以下代码控制拖拽元素的位置
if (this._status & STATUS.DRAG) {
@ -1035,12 +1035,19 @@ class GaiaContainer extends LitElement {
this.changeState(childToInsert, 'added', callback)
// this.synchronise()
childToInsert.synchroniseContainer()
const setGridId = () => {
childToInsert.gridId = this.getGridIdByCoordinate(
childToInsert._lastMasterLeft!,
childToInsert._lastMasterTop!,
childToInsert.pagination
)
}
if (this.ready) {
setGridId()
} else {
this.firstUpdated().then(setGridId)
}
}
/**
* Used to execute a state-change of a child that may possibly be animated.
@ -1250,8 +1257,8 @@ class GaiaContainer extends LitElement {
// this._dnd.child.isStatic = false
// this._dnd.child.container.style.position = "fixed";
let rect = this.getBoundingClientRect()
this._dnd.child.container.style.top = rect.top + 'px'
this._dnd.child.container.style.left = rect.left - this.offsetX + 'px'
// this._dnd.child.container.style.top = rect.top + 'px'
// this._dnd.child.container.style.left = rect.left - this.offsetX + 'px'
this._dnd.pagination = this.pagination
}

View File

@ -449,7 +449,7 @@ export default class GaiaContainerFolder extends GaiaContainerChild {
position: unset !important;
}
::slotted(.folder.openning) .gaia-container-folder {
position: raletive;
position: relative;
}
::slotted(.folder.initializing) .gaia-container-child {
position: unset !important;

View File

@ -62,11 +62,25 @@ export class PanelContainer extends LitElement {
// dock
this.dock = new StarDock()
this.shadowRoot?.appendChild(this.dock)
this.dock.sortMode = true
//
;(window as any).dock = this.dock
;(window as any).container = this.container
;(window as any).home = this
// this.addAppIcon(2, 2)
this.addEventListener('out-of-container', this)
this.addEventListener('drag-return-container', this)
this.addEventListener('out-of-dock', this)
this.addEventListener('drag-start', this)
this.addEventListener('drag-move', this)
this.addEventListener('drag-end', this)
this.parentElement!.addEventListener('animationend', () => {
this.container.changeLayout()
console.log('abc')
this.addAppIcon(1, 1)
let promise = new Promise((res) => {
res(undefined)
@ -80,14 +94,7 @@ export class PanelContainer extends LitElement {
})
}
this.container.synchronise()
// this.addAppIcon(2, 2)
this.addEventListener('out-of-container', this)
this.addEventListener('drag-return-container', this)
this.addEventListener('out-of-dock', this)
this.addEventListener('drag-start', this)
this.addEventListener('drag-move', this)
this.addEventListener('drag-end', this)
})
}
elementPlaceholder: HTMLElement = document.createElement('div')
@ -99,13 +106,14 @@ export class PanelContainer extends LitElement {
case 'out-of-container':
if (!this.prepareToTransfer) return
this.dragInDock = this.dock.dragIn({
...evt.detail,
element: this.elementPlaceholder,
centerX: evt.detail.centerX,
centerY: evt.detail.centerY,
})
break
case 'drag-return-container':
this.dock.dragOut(this.elementPlaceholder)
this.dragInDock = false
this.dock.dragOut(evt.detail)
break
case 'out-of-dock':
break
@ -119,9 +127,10 @@ export class PanelContainer extends LitElement {
if (this.dragInDock) {
this.dragInDock = false
if (this.dock._temporaryChild) {
this.dock._temporaryChild.element = evt.detail.target
this.dock._temporaryChild = undefined
if (this.dock._dragChild) {
this.dock._dragChild.element = evt.detail.target
this.dock._dragChild.container.classList.remove('dragging')
this.dock._dragChild = undefined
}
this.container.removeContainerChild(evt.detail.target)
}
@ -182,9 +191,16 @@ export class PanelContainer extends LitElement {
css`
:host {
display: flex;
position: relative;
flex-direction: column;
justify-content: space-around;
height: 100vh;
/** 控制组件容器的位置 */
--container-margin-top: 30px;
--container-margin-left: 20px;
/** 控制 dock 距离屏幕底部的距离 */
--dock-bottom: 10px;
}
star-container {
height: 80vh;
@ -202,10 +218,12 @@ export class PanelContainer extends LitElement {
top: 0;
left: 0;
display: grid;
margin-top: var(--container-margin-top);
margin-left: var(--container-margin-left);
grid-template-rows: repeat(6, 16.66%);
grid-template-columns: repeat(4, 25%);
height: 80vh;
width: 100vw;
width: calc(100vw - var(--container-margin-left) * 2);
z-index: 100;
background-color: rgba(1, 1, 0, 0.05);
}
@ -222,6 +240,7 @@ export class PanelContainer extends LitElement {
}
.gaia-container-child {
border: 1px solid black;
box-sizing: border-box;
}
`,
homescreenStyle,