TASK: #104293 - add homescreen function for storing apps' order

This commit is contained in:
luojiahao 2022-09-19 09:46:47 +08:00
parent b12d05000e
commit 0d64d0e603
14 changed files with 385 additions and 125 deletions

View File

@ -20,3 +20,4 @@
- add dock - add dock
- add function for dragging icon into dock - add function for dragging icon into dock
- add function for dragging icon into container - add function for dragging icon into container
- add homescreen function for storing apps' order

View File

@ -23,6 +23,10 @@ export default class DockChild {
x: 0, x: 0,
y: 0, y: 0,
} }
// 状态计时器
removed: number | undefined = undefined
added: number | undefined = undefined
constructor(_element: HTMLElement, manager: StarDock) { constructor(_element: HTMLElement, manager: StarDock) {
this._element = _element this._element = _element
this.manager = manager this.manager = manager
@ -82,6 +86,7 @@ export default class DockChild {
this._lastMasterWidth = this._master.offsetWidth this._lastMasterWidth = this._master.offsetWidth
this._lastMasterHeight = this._master.offsetHeight this._lastMasterHeight = this._master.offsetHeight
this.center.x = left + this._lastMasterWidth / 2 this.center.x = left + this._lastMasterWidth / 2
this.center.y = top + this._lastMasterHeight / 2
!this.container.classList.contains('dragging') && !this.container.classList.contains('dragging') &&
(container.style.transform = 'translate(' + left + 'px, ' + top + 'px)') (container.style.transform = 'translate(' + left + 'px, ' + top + 'px)')
} }

View File

@ -1,5 +1,5 @@
import {html, css, LitElement, CSSResultGroup} from 'lit' import {html, css, LitElement, CSSResultGroup} from 'lit'
import {customElement, query, state} from 'lit/decorators.js' import {customElement, query} from 'lit/decorators.js'
import DockChild from './dock-child' import DockChild from './dock-child'
import customStyle from './style' import customStyle from './style'
interface DragAndDrop { interface DragAndDrop {
@ -59,6 +59,7 @@ interface DragAndDrop {
const DND_MOVE_THROTTLE = 50 const DND_MOVE_THROTTLE = 50
const DND_THRESHOLD = 5 const DND_THRESHOLD = 5
const DEFAULT_DND_TIMEOUT = 300 const DEFAULT_DND_TIMEOUT = 300
const STATE_CHANGE_TIMEOUT = 100
@customElement('star-dock') @customElement('star-dock')
export default class StarDock extends LitElement { export default class StarDock extends LitElement {
@ -111,27 +112,40 @@ export default class StarDock extends LitElement {
}, },
} }
appendContainerChild = (element: HTMLElement, options?: number) => { appendContainerChild = (
element: HTMLElement,
order?: number,
callback?: Function
) => {
const child = const child =
this.getChildByElement(element) || new DockChild(element, this) this.getChildByElement(element) || new DockChild(element, this)
this._children.push(child) this._children.push(child)
this.container.appendChild(child.master) this.container.appendChild(child.master)
if (typeof order == 'number') {
child.order = order
}
this.changeState(child, 'added', callback)
} }
removeContainerChild = (element: HTMLElement) => { removeContainerChild = (element: HTMLElement, callback?: Function) => {
const target = this.getChildByElement(element) const target = this.getChildByElement(element)
if (!target) return null if (!target) return null
this._children = this._children.filter((child) => child !== target) this._children = this._children.filter((child) => child !== target)
this.changeState(target, 'removed', callback)
target.master.remove() target.master.remove()
return target return target
} }
realRemoveChild = (element: HTMLElement) => {} realRemoveChild = (element: HTMLElement) => {
element
}
realInsertBefore = (element: HTMLElement, reference: HTMLElement) => { realInsertBefore = (element: HTMLElement, reference: HTMLElement) => {
element.style.order = reference.style.order
this.container.insertBefore(element, reference) this.container.insertBefore(element, reference)
} }
realInsertAfter = (element: HTMLElement, reference: HTMLElement) => { realInsertAfter = (element: HTMLElement, reference: HTMLElement) => {
element.style.order = reference.style.order
if (reference.nextSibling) { if (reference.nextSibling) {
this.container.insertBefore(element, reference.nextElementSibling) this.container.insertBefore(element, reference.nextElementSibling)
} else { } else {
@ -142,10 +156,14 @@ export default class StarDock extends LitElement {
synchronise() { synchronise() {
for (const child of this._children) { for (const child of this._children) {
child.master.style.order = String(child.order)
child.synchroniseContainer() child.synchroniseContainer()
} }
this.reorderChild() this.reorderChild()
if (this._children.length) {
this._gridSize = this._children[0].master.offsetWidth
}
} }
reorderChild() { reorderChild() {
@ -188,7 +206,6 @@ export default class StarDock extends LitElement {
handleEvent(event: TouchEvent) { handleEvent(event: TouchEvent) {
switch (event.type) { switch (event.type) {
case 'touchstart': case 'touchstart':
case 'click':
case 'mousedown': case 'mousedown':
if (this._dnd.active || this._dnd.timeout) { if (this._dnd.active || this._dnd.timeout) {
break break
@ -217,7 +234,10 @@ export default class StarDock extends LitElement {
// Find the child // Find the child
let children = [...this._children] let children = [...this._children]
for (let child of children) { for (let child of children) {
if (child.element === target || child.master === target) { if (
child.master === target ||
child.master.compareDocumentPosition(target) & 16
) {
this._dnd.child = child this._dnd.child = child
break break
} }
@ -276,6 +296,8 @@ export default class StarDock extends LitElement {
this._dnd.center.x + (this._dnd.last.pageX - this._dnd.start.pageX) this._dnd.center.x + (this._dnd.last.pageX - this._dnd.start.pageX)
const centerY = const centerY =
this._dnd.center.y + (this._dnd.last.pageY - this._dnd.start.pageY) this._dnd.center.y + (this._dnd.last.pageY - this._dnd.start.pageY)
const gridHeight = this._dnd.child.master.offsetHeight
const gridWidth = this._dnd.child.master.offsetWidth
if (centerY < this.offsetTop) { if (centerY < this.offsetTop) {
this.dropPosition = 'outter' this.dropPosition = 'outter'
this.dispatchEvent( this.dispatchEvent(
@ -284,6 +306,8 @@ export default class StarDock extends LitElement {
target: this._dnd.child.element, target: this._dnd.child.element,
centerX, centerX,
centerY, centerY,
gridHeight,
gridWidth,
}, },
composed: true, composed: true,
}) })
@ -313,7 +337,7 @@ export default class StarDock extends LitElement {
event.preventDefault() event.preventDefault()
} }
this.endDrag(event) this.endDrag()
break break
case 'contextmenu': case 'contextmenu':
@ -346,6 +370,8 @@ export default class StarDock extends LitElement {
element: HTMLElement | DockChild element: HTMLElement | DockChild
centerX: number centerX: number
centerY: number centerY: number
gridHeight: number
gridWidth: number
}) => { }) => {
const {element, centerX, centerY} = info const {element, centerX, centerY} = info
const dropChild = this.getChildFromPoint(centerX, centerY) const dropChild = this.getChildFromPoint(centerX, centerY)
@ -362,9 +388,9 @@ export default class StarDock extends LitElement {
child.container.style.transform = child.container.style.transform =
'translate(' + 'translate(' +
(centerX - (child.master.offsetWidth || this._gridSize) / 2) + (centerX - this._gridSize / 2) +
'px, ' + 'px, ' +
(centerY - (child.master.offsetHeight || this._gridSize) / 2) + (centerY - this._gridSize / 2) +
'px)' 'px)'
if (!dropChild) { if (!dropChild) {
@ -391,6 +417,7 @@ export default class StarDock extends LitElement {
if (this.container.lastElementChild == this._dragChild!.master) if (this.container.lastElementChild == this._dragChild!.master)
return true return true
this._dragChild!.order = this._children.length
this.container.appendChild(this._dragChild?.master!) this.container.appendChild(this._dragChild?.master!)
} }
} }
@ -503,18 +530,28 @@ export default class StarDock extends LitElement {
this._dnd.center.x + (this._dnd.last.pageX - this._dnd.start.pageX), this._dnd.center.x + (this._dnd.last.pageX - this._dnd.start.pageX),
centerY: centerY:
this._dnd.center.y + (this._dnd.last.pageY - this._dnd.start.pageY), this._dnd.center.y + (this._dnd.last.pageY - this._dnd.start.pageY),
gridHeight: this._dnd.child.master.offsetHeight,
gridWidth: this._dnd.child.master.offsetWidth,
}) })
} }
endDrag = (evt: TouchEvent) => { endDrag = () => {
if (this._dnd.active) { if (this._dnd.active) {
const centerX = const centerX =
this._dnd.center.x + (this._dnd.last.pageX - this._dnd.start.pageX) this._dnd.center.x + (this._dnd.last.pageX - this._dnd.start.pageX)
const centerY = const centerY =
this._dnd.center.y + (this._dnd.last.pageY - this._dnd.start.pageY) this._dnd.center.y + (this._dnd.last.pageY - this._dnd.start.pageY)
const gridHeight = this._dnd.child.master.offsetHeight
const gridWidth = this._dnd.child.master.offsetWidth
this.dispatchEvent( this.dispatchEvent(
new CustomEvent('dock-drag-end', { new CustomEvent('dock-drag-end', {
detail: {target: this._dnd.child.element, centerX, centerY}, detail: {
target: this._dnd.child.element,
centerX,
centerY,
gridHeight,
gridWidth,
},
composed: true, composed: true,
}) })
) )
@ -534,7 +571,9 @@ export default class StarDock extends LitElement {
/** /**
* *
*/ */
getElementInfo = (element: HTMLElement) => {} getElementInfo = (element: HTMLElement) => {
element
}
/** /**
* *
@ -561,8 +600,6 @@ export default class StarDock extends LitElement {
} }
protected firstUpdated(): void { protected firstUpdated(): void {
this._gridSize = 100
this.container.addEventListener('touchstart', this) this.container.addEventListener('touchstart', this)
this.container.addEventListener('click', this) this.container.addEventListener('click', this)
this.container.addEventListener('touchmove', this) this.container.addEventListener('touchmove', this)
@ -577,6 +614,57 @@ export default class StarDock extends LitElement {
this._containerOffset.left = this.container.offsetLeft this._containerOffset.left = this.container.offsetLeft
} }
changeState(
child: DockChild,
state: 'added' | 'removed',
callback?: Function
) {
// Check that the child is still attached to this parent (can happen if
// the child is removed while frozen).
if (
(child.master.parentElement as HTMLElement).parentNode !== this.shadowRoot
) {
return
}
// Check for a redundant state change.
if (child.container.classList.contains(state)) {
return
}
let animStart = (e: AnimationEvent) => {
if (!e.animationName.endsWith(state)) {
return
}
child.container.removeEventListener('animationstart', animStart)
window.clearTimeout(child[state])
delete child[state]
// let self = this;
child.container.addEventListener('animationend', function animEnd() {
child.container.removeEventListener('animationend', animEnd)
child.container.classList.remove(state)
if (callback) {
callback()
}
})
}
child.container.addEventListener('animationstart', animStart)
child.container.classList.add(state)
child[state] = window.setTimeout(() => {
delete child[state]
child.container.removeEventListener('animationstart', animStart)
child.container.classList.remove(state)
if (callback) {
callback()
}
}, STATE_CHANGE_TIMEOUT)
}
protected render() { protected render() {
return html` return html`
<div id="container"></div> <div id="container"></div>
@ -589,24 +677,25 @@ export default class StarDock extends LitElement {
// position: relative; // position: relative;
display: flex; display: flex;
flex: 1; flex: 1;
margin-bottom: var(--dock-margin-bottom, 60px); margin-bottom: var(--dock-margin-bottom);
width: 100%; width: 100%;
height: var(--dock-height, 92px); height: var(--dock-container-height);
} }
#container { #container {
display: flex; display: flex;
justify-content: center; justify-content: center;
margin: auto auto 0; margin: auto auto 0;
min-width: var(--dock-min-width, 760px); min-width: var(--dock-min-width, 100%);
max-width: 100%; max-width: 100%;
height: var(--dock-height, 92px); height: var(--dock-container-height);
background-color: rgba(1, 1, 1, 0.5); background-color: rgba(1, 1, 1, 0.5);
} }
.dock-child-master { .dock-child-master {
max-width: var(--dock-grid-width, 92px); max-width: var(--dock-grid-width, 92px);
max-height: var(--dock-grid-width, 92px);
flex: 1; flex: 1;
} }
@ -615,8 +704,10 @@ export default class StarDock extends LitElement {
top: 0; top: 0;
left: 0; left: 0;
z-index: 0; z-index: 0;
width: var(--icon-size);
height: var(--icon-size);
} }
.dock-child-container:not(.dragging) { .dock-child-container:not(.dragging):not(.added) {
transition: transform 0.2s; transition: transform 0.2s;
} }
.dock-child-container.dragging { .dock-child-container.dragging {

View File

@ -7,9 +7,10 @@ export default css`
/** dock 与屏幕底部距离 */ /** dock 与屏幕底部距离 */
--dock-margin-bottom: var(--dock-bottom, 30px); --dock-margin-bottom: var(--dock-bottom, 30px);
/** dock 最小宽度 */ /** dock 最小宽度 */
--dock-min-width: 760px; // --dock-min-width: 760px;
--dock-min-width: 100%;
/** dock 高度 */ /** dock 高度 */
--dock-height: 140px; --dock-container-height: var(--dock-height, 140px);
/** dock 图标宽度 */ /** dock 图标宽度 */
--dock-grid-width: var(--icon-size, 100px); --dock-grid-width: var(--icon-size, 100px);
} }

View File

@ -55,12 +55,12 @@ const DND_THRESHOLD = 5
/** /**
* *
*/ */
const MERGE_FORDER_TIME = 700 // const MERGE_FORDER_TIME = 700
/** /**
* *
*/ */
const FORDER_OPERATE_TIME = 500 // const FORDER_OPERATE_TIME = 500
/** /**
* *
@ -91,9 +91,9 @@ const cubic_bezier = (
) )
} }
@customElement('star-container') @customElement('gaia-container')
class GaiaContainer extends LitElement { class GaiaContainer extends LitElement {
name: string = 'star-container' name: string = 'gaia-container'
row: number = 6 row: number = 6
column: number = 4 column: number = 4
_frozen: boolean = false _frozen: boolean = false
@ -275,7 +275,9 @@ class GaiaContainer extends LitElement {
} }
} }
} }
window.addEventListener('resize', this.changeLayout) window.addEventListener('resize', () => {
this.firstUpdated().then(this.changeLayout)
})
this.dndObserver = new MutationObserver(dndObserverCallback) this.dndObserver = new MutationObserver(dndObserverCallback)
dndObserverCallback() dndObserverCallback()
@ -354,7 +356,7 @@ class GaiaContainer extends LitElement {
// 同时触发划动时,不同触发原因的优先级顺序,越后越优先 // 同时触发划动时,不同触发原因的优先级顺序,越后越优先
typeIndex = ['reset', 'swipe', 'mouseup', 'touchend'] typeIndex = ['reset', 'swipe', 'mouseup', 'touchend']
// 划动计时器 // 划动计时器
timer: number | undefined = undefined timer: Function | undefined = undefined
// 触发划动的原因类型 // 触发划动的原因类型
slideType: string = '' slideType: string = ''
@ -362,20 +364,29 @@ class GaiaContainer extends LitElement {
if ( if (
!element || !element ||
this.typeIndex.indexOf(this.slideType) > this.typeIndex.indexOf(type) this.typeIndex.indexOf(this.slideType) > this.typeIndex.indexOf(type)
) ) {
return return
}
if (this.timer) { if (this.timer) {
clearInterval(this.timer)
this.slideType = '' this.slideType = ''
} }
const targetPagination = this.pages.indexOf(element)
const animateTime = 450 const animateTime = 450
const target = -element.offsetLeft const target = -element.offsetLeft
const origin = this.offsetX const origin = this.offsetX
// 移动总距离 // 移动总距离
const distance = target - origin const distance = target - origin
const startTime = new Date().getTime() const startTime = new Date().getTime()
this.dispatchEvent(
new CustomEvent('pagination-change', {
detail: targetPagination,
composed: true,
})
)
this.slideType = type this.slideType = type
this.timer = setInterval(() => { this.timer = () => {
requestAnimationFrame(() => { requestAnimationFrame(() => {
const cur = new Date().getTime() const cur = new Date().getTime()
const t = (cur - startTime) / animateTime const t = (cur - startTime) / animateTime
@ -394,7 +405,7 @@ class GaiaContainer extends LitElement {
this._dnd.top + this._dnd.top +
'px)' 'px)'
// 控制与文件夹交互时的拖拽操作 // 控制与文件夹交互时的拖拽操作
const child = this._dnd.child as unknown as GaiaContainerChild // const child = this._dnd.child as unknown as GaiaContainerChild
// if (child) { // if (child) {
// child.container.style.setProperty( // child.container.style.setProperty(
// '--offset-position-left', // '--offset-position-left',
@ -408,7 +419,7 @@ class GaiaContainer extends LitElement {
} }
if (ratio >= 1) { if (ratio >= 1) {
clearInterval(this.timer) // clearInterval(this.timer)
this.timer = undefined this.timer = undefined
this.slideType = '' this.slideType = ''
this.offsetX = target this.offsetX = target
@ -420,9 +431,12 @@ class GaiaContainer extends LitElement {
this.status &= ~STATUS.SWIPE this.status &= ~STATUS.SWIPE
// 如果处于拖拽状态则更新一次落点位置 // 如果处于拖拽状态则更新一次落点位置
this.continueDrag() this.continueDrag()
} else if (this.timer) {
this.timer()
} }
}) })
}, 10) }
this.timer()
} }
destroyFolder = (evt: Event) => { destroyFolder = (evt: Event) => {
@ -439,6 +453,12 @@ class GaiaContainer extends LitElement {
return this.shadowRoot && this.shadowRoot.querySelector('style') return this.shadowRoot && this.shadowRoot.querySelector('style')
} }
get elements() {
return this._children.map((child) => {
return child.element
})
}
get offsetX() { get offsetX() {
return this._offsetX return this._offsetX
} }
@ -595,6 +615,7 @@ class GaiaContainer extends LitElement {
getFolderGridIdByCoordinate(x: number, y: number, pagination: number = 0) { getFolderGridIdByCoordinate(x: number, y: number, pagination: number = 0) {
if (!this.openedFolder || !this.openedFolder._status) return -1 if (!this.openedFolder || !this.openedFolder._status) return -1
let page = this.pages[this.openedFolder.pagination] let page = this.pages[this.openedFolder.pagination]
pagination
x += page.scrollLeft - page.offsetLeft - this.openedFolder._lastMasterLeft! x += page.scrollLeft - page.offsetLeft - this.openedFolder._lastMasterLeft!
y += page.scrollTop - page.offsetTop - this.openedFolder._lastMasterTop! y += page.scrollTop - page.offsetTop - this.openedFolder._lastMasterTop!
@ -663,7 +684,6 @@ class GaiaContainer extends LitElement {
} }
} else { } else {
// 不是拖拽中的元素,放入下一页 // 不是拖拽中的元素,放入下一页
this.realRemoveChild(element)
this.realAppendChild(++pagination, element) this.realAppendChild(++pagination, element)
} }
} }
@ -734,7 +754,7 @@ class GaiaContainer extends LitElement {
// 越界后重新拿出来放入下一个页面 // 越界后重新拿出来放入下一个页面
if (!this._dnd.child && pagination) { if (!this._dnd.child && pagination) {
// 非拖拽元素导致的越界,后插元素直接加入下一个页面 // 非拖拽元素导致的越界,后插元素直接加入下一个页面
return this.realAppendChild(+pagination + 1, arguments[0]) return this.realAppendChild(+pagination + 1, args[0])
} }
// 同时记录被后插元素为无法交换元素 // 同时记录被后插元素为无法交换元素
@ -785,14 +805,8 @@ class GaiaContainer extends LitElement {
removeContainerChild(element: HTMLElement, callback?: Function) { removeContainerChild(element: HTMLElement, callback?: Function) {
let children = this._children let children = this._children
let childToRemove: GaiaContainerChild | null = null let childToRemove: GaiaContainerChild | null =
this.getChildByElement(element)
for (let child of children) {
if (child.element === element) {
childToRemove = child
break
}
}
if (childToRemove === null) { if (childToRemove === null) {
throw 'removeChild called on unknown child' throw 'removeChild called on unknown child'
@ -1012,11 +1026,9 @@ class GaiaContainer extends LitElement {
let referenceIndex = -1 let referenceIndex = -1
if (reference !== null) { if (reference !== null) {
for ( for (let i = 0, iLen = children.length; i < iLen; i++) {
let i = 0, child = children[i], iLen = children.length; const child = children[i]
i < iLen;
i++
) {
if (child.element === reference) { if (child.element === reference) {
referenceIndex = i referenceIndex = i
break break
@ -1036,12 +1048,11 @@ class GaiaContainer extends LitElement {
} }
} }
children.push(childToInsert)
if (folderName && this.folders[folderName]) { if (folderName && this.folders[folderName]) {
// 属于文件夹内的图标 // 属于文件夹内的图标
children.push(childToInsert)
this.folders[folderName].addAppIcon(childToInsert.master) this.folders[folderName].addAppIcon(childToInsert.master)
} else if (referenceIndex === -1) { } else if (referenceIndex === -1) {
children.push(childToInsert)
this.realAppendChild(pagination ?? 0, childToInsert.master) this.realAppendChild(pagination ?? 0, childToInsert.master)
} else { } else {
const referenceNode = children[referenceIndex].folderName const referenceNode = children[referenceIndex].folderName
@ -1053,21 +1064,25 @@ class GaiaContainer extends LitElement {
if (folderName && !this.folders[folderName]) { if (folderName && !this.folders[folderName]) {
// 有文件夹名但不存在该文件夹,则将该图标转化为文件夹 // 有文件夹名但不存在该文件夹,则将该图标转化为文件夹
this.appToFolder(childToInsert.master) // this.appToFolder(childToInsert.master)
} }
childToInsert.isWidget && (childToInsert.isStatic = true) childToInsert.isWidget && (childToInsert.isStatic = true)
this.changeState(childToInsert, 'added', callback) this.changeState(childToInsert, 'added', callback)
// this.synchronise() // this.synchronise()
childToInsert.synchroniseContainer()
const setGridId = () => { const setGridId = () => {
if (anchorCoordinate) {
childToInsert.anchor()
} else {
childToInsert.gridId = this.getGridIdByCoordinate( childToInsert.gridId = this.getGridIdByCoordinate(
childToInsert._lastMasterLeft!, childToInsert.master.offsetLeft,
childToInsert._lastMasterTop!, childToInsert.master.offsetTop,
childToInsert.pagination childToInsert.pagination
) )
} }
childToInsert.synchroniseContainer()
}
if (this.ready) { if (this.ready) {
setGridId() setGridId()
} else { } else {
@ -1190,7 +1205,8 @@ class GaiaContainer extends LitElement {
// TODO还需要考虑跨文件夹移动 // TODO还需要考虑跨文件夹移动
if ( if (
typeof child?.pagination == 'number' && typeof child?.pagination == 'number' &&
child.pagination !== this._dnd.child.pagination child.pagination !== this._dnd.child.pagination &&
this._dnd.child
) { ) {
// 当被选中元素与被移动元素页码不一致时,+ // 当被选中元素与被移动元素页码不一致时,+
this._dnd.isSpanning = true this._dnd.isSpanning = true
@ -1282,7 +1298,7 @@ class GaiaContainer extends LitElement {
this._dnd.child.container.classList.add('dragging') this._dnd.child.container.classList.add('dragging')
// this._dnd.child.isStatic = false // this._dnd.child.isStatic = false
// this._dnd.child.container.style.position = "fixed"; // this._dnd.child.container.style.position = "fixed";
let rect = this.getBoundingClientRect() // let rect = this.getBoundingClientRect()
// this._dnd.child.container.style.top = rect.top + 'px' // 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.left = rect.left - this.offsetX + 'px'
this._dnd.pagination = this.pagination this._dnd.pagination = this.pagination
@ -1375,28 +1391,24 @@ class GaiaContainer extends LitElement {
const distanceY = const distanceY =
gridY ?? gridY ??
this._dnd.gridPosition.y + this._dnd.last.pageY - this._dnd.start.pageY this._dnd.gridPosition.y + this._dnd.last.pageY - this._dnd.start.pageY
let {dropTarget, dropChild, isPage, pagination, gridId} = let {dropTarget, dropChild, isPage, gridId} = this.getChildFromPoint(
this.getChildFromPoint(distanceX, distanceY) distanceX,
distanceY
)
const child = this._dnd.child const child = this._dnd.child
let dropStatus = false let dropStatus = false
// while (gridId < 0) {
// gridId += this.column
// }
// while (gridId > 23) {
// gridId -= this.column
// }
if ( if (
this._staticElements.includes(dropTarget) && this._staticElements.includes(dropTarget) &&
(child !== dropChild || (gridId == dropChild.gridId && mode == 'delay')) (child !== dropChild || (gridId == dropChild.gridId && mode == 'delay'))
) { ) {
// @ts-ignore
return return
} }
this._dnd.dropTarget = dropTarget this._dnd.dropTarget = dropTarget
// 拖拽元素悬浮页面默认为当前页面 // 拖拽元素悬浮页面默认为当前页面
const suspendingPage = isPage ? dropTarget! : this.pages[this.pagination] // const suspendingPage = isPage ? dropTarget! : this.pages[this.pagination]
if (child.isTail && isPage) { if (child.isTail && isPage) {
if (this._dnd.lastDropChild) { if (this._dnd.lastDropChild) {
this._dnd.lastDropChild.master.classList.remove('merging') this._dnd.lastDropChild.master.classList.remove('merging')
@ -1624,6 +1636,8 @@ class GaiaContainer extends LitElement {
centerY: number centerY: number
row: number row: number
column: number column: number
gridHeight: number
gridWidth: number
} }
) { ) {
this.dragInType = DragInType.PAGES this.dragInType = DragInType.PAGES
@ -1646,20 +1660,13 @@ class GaiaContainer extends LitElement {
this._dnd.child.container.classList.add('dragging') this._dnd.child.container.classList.add('dragging')
} }
centerX = let containerX = centerX - this.gridWidth / 2 - this.left
centerX - let containerY = centerY - this.gridHeight / 2 - this.top
(this._dnd.child.column / 2 + 0.25) * this.gridWidth +
this.left +
page.offsetLeft
centerY =
centerY -
(this._dnd.child.row / 2 + 0.5) * this.gridHeight +
this.top +
page.offsetTop
gridX = centerX + this.gridWidth / 2
gridY = centerY + this.gridHeight / 2
this._dnd.child.container.style.transform = `translate(${centerX}px, ${centerY}px)` gridX = containerX + this.gridWidth / 2
gridY = containerY + this.gridHeight / 2
this._dnd.child.container.style.transform = `translate(${containerX}px, ${containerY}px)`
} }
this.dropElement(mode, true, gridX, gridY) this.dropElement(mode, true, gridX, gridY)
} }
@ -1772,6 +1779,17 @@ class GaiaContainer extends LitElement {
endDrag(event: Event) { endDrag(event: Event) {
if (this._dnd.active) { if (this._dnd.active) {
const centerX =
this.left +
this._dnd.center.x +
(this._dnd.last.pageX - this._dnd.start.pageX)
const centerY =
this.top +
this._dnd.center.y +
(this._dnd.last.pageY - this._dnd.start.pageY)
const elementHeight = this._dnd.child.element.offsetHeight
const elementWidth = this._dnd.child.element.offsetWidth
this.dropElement('immediately') this.dropElement('immediately')
this.dispatchEvent( this.dispatchEvent(
new CustomEvent('drag-end', { new CustomEvent('drag-end', {
@ -1783,13 +1801,24 @@ class GaiaContainer extends LitElement {
pageY: this._dnd.last.pageY, pageY: this._dnd.last.pageY,
clientX: this._dnd.last.clientX, clientX: this._dnd.last.clientX,
clientY: this._dnd.last.clientY, clientY: this._dnd.last.clientY,
centerX,
centerY,
gridHeight: elementHeight,
gridWidth: elementWidth,
}, },
composed: true, composed: true,
}) })
) )
} else if (this._dnd.timeout !== null && this._dnd.child?.isFolder) { } else if (this._dnd.timeout !== null && this._dnd.child?.isFolder) {
this._dnd.child.open() this._dnd.child.open()
} else if (this._dnd.timeout !== null && this._dnd.child?.element) { } else if (
!(
this.status & // 不处于四种状态的任何一种时,才可以发送 activate 事件
(STATUS.DRAG | STATUS.SWIPE | STATUS.TURN | STATUS.SORT)
) &&
this._dnd.timeout !== null &&
this._dnd.child?.element
) {
let handled = !this.dispatchEvent( let handled = !this.dispatchEvent(
new CustomEvent('activate', { new CustomEvent('activate', {
cancelable: true, cancelable: true,
@ -1834,7 +1863,6 @@ class GaiaContainer extends LitElement {
case 'mousedown': case 'mousedown':
this.istouching = true this.istouching = true
if (this.timer) { if (this.timer) {
clearInterval(this.timer)
this.timer = undefined this.timer = undefined
this.slideType = '' this.slideType = ''
} }
@ -1944,15 +1972,17 @@ class GaiaContainer extends LitElement {
} }
if (this._dnd.child?.priority == 1 && !this._dnd.child.isFolder) { if (this._dnd.child?.priority == 1 && !this._dnd.child.isFolder) {
const {top, left} = this.getBoundingClientRect()
const centerX = const centerX =
left + this.left +
this._dnd.center.x + this._dnd.center.x +
(this._dnd.last.pageX - this._dnd.start.pageX) (this._dnd.last.pageX - this._dnd.start.pageX)
const centerY = const centerY =
top + this.top +
this._dnd.center.y + this._dnd.center.y +
(this._dnd.last.pageY - this._dnd.start.pageY) (this._dnd.last.pageY - this._dnd.start.pageY)
const elementHeight = this._dnd.child.element.offsetHeight
const elementWidth = this._dnd.child.element.offsetWidth
if (centerY > this.offsetHeight) { if (centerY > this.offsetHeight) {
this.dragPosition = 'outter' this.dragPosition = 'outter'
if (!this.throttle) { if (!this.throttle) {
@ -1961,8 +1991,8 @@ class GaiaContainer extends LitElement {
new CustomEvent('out-of-container', { new CustomEvent('out-of-container', {
detail: { detail: {
element: this._dnd.child?.element, element: this._dnd.child?.element,
gridHeight: this.gridHeight, gridHeight: elementHeight,
gridWidth: this.gridWidth, gridWidth: elementWidth,
centerX: centerX, centerX: centerX,
centerY: centerY, centerY: centerY,
}, },
@ -2181,7 +2211,7 @@ class GaiaContainer extends LitElement {
declare global { declare global {
interface HTMLElementTagNameMap { interface HTMLElementTagNameMap {
'star-container': GaiaContainer 'gaia-container': GaiaContainer
} }
} }

View File

@ -333,7 +333,7 @@ export default class ExchangeStrategy {
if (gridId === undefined) { if (gridId === undefined) {
ergodic(-1 * direction) ergodic(-1 * direction)
} }
return gridId !== undefined ? [[gridId]] : gridId return gridId !== undefined ? [[gridId]] : undefined
} }
const place = () => { const place = () => {
@ -529,6 +529,7 @@ export default class ExchangeStrategy {
placedRecorder?: PlacedRecorder placedRecorder?: PlacedRecorder
childCoordinate?: GaiaContainer['childCoordinate'] childCoordinate?: GaiaContainer['childCoordinate']
} { } {
forceDrop
const {column: mColumn} = this.manager const {column: mColumn} = this.manager
// 子节点右下角单元格所在的网格ID // 子节点右下角单元格所在的网格ID
const edge_bottom_right = const edge_bottom_right =
@ -546,7 +547,7 @@ export default class ExchangeStrategy {
this.placedRecorder = {} this.placedRecorder = {}
this.placedChild.add(child) this.placedChild.add(child)
this.pickChild(child, recorder) this.pickChild(child, recorder)
const dropChild = recorder[gridId] // const dropChild = recorder[gridId]
// 获取被挤占位置的元素,按照优先级排序 // 获取被挤占位置的元素,按照优先级排序
const pickChildren = this.getChildrenByGridArea( const pickChildren = this.getChildrenByGridArea(
gridId, gridId,

View File

@ -32,7 +32,7 @@ export default class GaiaContainerFolder extends GaiaContainerChild {
gridWidth: number = 0 gridWidth: number = 0
constructor(manager: GaiaContainer, name?: string) { constructor(manager: GaiaContainer, name?: string) {
super(null, 1, 1, undefined, manager) super(undefined, 1, 1, undefined, manager)
this.name = this.checkAndGetFolderName(name) this.name = this.checkAndGetFolderName(name)
this._id = `folder-${new Date().getTime()}` this._id = `folder-${new Date().getTime()}`
this.init() this.init()
@ -375,6 +375,7 @@ export default class GaiaContainerFolder extends GaiaContainerChild {
handleEvent(evt: TouchEvent) { handleEvent(evt: TouchEvent) {
switch (evt.type) { switch (evt.type) {
// @ts-ignore
case 'touchend': case 'touchend':
if (this._status && evt.target === this.container) { if (this._status && evt.target === this.container) {
this.close() this.close()

View File

@ -66,6 +66,7 @@ class GaiaContainerPage {
div.className = `gaia-container-page` div.className = `gaia-container-page`
div.style.setProperty('--pagination', pagination) div.style.setProperty('--pagination', pagination)
// 影子页面,用于检测旋转屏幕后能否仍容纳当前页面的子节点
const shadowPage = div.cloneNode() as HTMLElement const shadowPage = div.cloneNode() as HTMLElement
shadowPage.classList.add('shadow') shadowPage.classList.add('shadow')
this._shadowPagesMap.set(div, shadowPage) this._shadowPagesMap.set(div, shadowPage)
@ -75,6 +76,14 @@ class GaiaContainerPage {
this.observe(div) this.observe(div)
this._manager.childCoordinate[this._pages.length - 1] = {} this._manager.childCoordinate[this._pages.length - 1] = {}
this._manager.dispatchEvent(
new CustomEvent('page-change', {
detail: {
addIndex: this._pages.indexOf(div),
},
composed: true,
})
)
return { return {
page: div, page: div,
shadowPage, shadowPage,
@ -103,6 +112,14 @@ class GaiaContainerPage {
return return
} }
delete this._pages[index] delete this._pages[index]
this._manager.dispatchEvent(
new CustomEvent('page-change', {
detail: {
deleteIndex: index,
},
composed: true,
})
)
if (index > -1) { if (index > -1) {
page?.remove?.() page?.remove?.()
let flag = false let flag = false
@ -130,6 +147,10 @@ class GaiaContainerPage {
callback(this._pages[i], i, this._pages) callback(this._pages[i], i, this._pages)
} }
} }
indexOf = (page: HTMLElement) => {
return this._pages.indexOf(page)
}
} }
export default GaiaContainerPage export default GaiaContainerPage

View File

@ -2,7 +2,5 @@ import {css} from 'lit'
export default css` export default css`
:host { :host {
/* 图标大小 */
--icon-size: 92px;
} }
` `

View File

@ -1,17 +1,20 @@
import {html, css, LitElement, TemplateResult} from 'lit' import {html, css, LitElement} from 'lit'
import {customElement, property, query, state} from 'lit/decorators.js' import {customElement, query, state} from 'lit/decorators.js'
import GaiaContainer from '../../../components/grid-container/container' import GaiaContainer from '../../../components/grid-container/container'
import GaiaContainerChild from '../../../components/grid-container/gaia-container-child' import GaiaContainerChild from '../../../components/grid-container/gaia-container-child'
import homescreenStyle from './homescreen-style' import homescreenStyle from './homescreen-style'
import StarDock from '../../../components/dock/dock' import StarDock from '../../../components/dock/dock'
import {IndicatorPagePoint} from '../../../components/indicator/indicator-page-point'
import './icon' import './icon'
import SiteIcon from './icon'
@customElement('panel-homescreen') @customElement('panel-homescreen')
export class PanelContainer extends LitElement { export class PanelContainer extends LitElement {
container!: GaiaContainer container!: GaiaContainer
dock!: StarDock dock!: StarDock
icons: {[prop: string]: GaiaContainerChild} = {} icons: {[prop: string]: GaiaContainerChild} = {}
pageIndicator!: IndicatorPagePoint
@query('.reset') resetBtn!: HTMLElement @query('.reset') resetBtn!: HTMLElement
@query('#row') rowInput!: HTMLInputElement @query('#row') rowInput!: HTMLInputElement
@query('#column') columnInput!: HTMLInputElement @query('#column') columnInput!: HTMLInputElement
@ -59,32 +62,71 @@ export class PanelContainer extends LitElement {
this.shadowRoot?.appendChild(this.container) this.shadowRoot?.appendChild(this.container)
this.container.sortMode = true this.container.sortMode = true
// 页面指示器
this.pageIndicator = new IndicatorPagePoint()
this.pageIndicator.edit = true
this.shadowRoot?.appendChild(this.pageIndicator)
// dock // dock
this.dock = new StarDock() this.dock = new StarDock()
this.shadowRoot?.appendChild(this.dock) this.shadowRoot?.appendChild(this.dock)
this.dock.sortMode = true this.dock.sortMode = true
// // 存储全局变量
;(window as any).dock = this.dock ;(window as any).dock = this.dock
;(window as any).container = this.container ;(window as any).container = this.container
;(window as any).home = this ;(window as any).home = this
// this.addAppIcon(2, 2) // container 相关事件
this.addEventListener('out-of-container', this) this.addEventListener('out-of-container', this)
this.addEventListener('drag-return-container', this) this.addEventListener('drag-return-container', this)
this.addEventListener('out-of-dock', this)
this.addEventListener('drag-start', this) this.addEventListener('drag-start', this)
this.addEventListener('drag-move', this) this.addEventListener('drag-move', this)
this.addEventListener('drag-end', this) this.addEventListener('drag-end', this)
this.addEventListener('drag-and-drop', this) this.addEventListener('drag-and-drop', this)
this.addEventListener('page-change', this)
this.addEventListener('pagination-change', this)
// dock 相关事件
this.addEventListener('out-of-dock', this)
this.addEventListener('dock-drag-start', this) this.addEventListener('dock-drag-start', this)
this.addEventListener('dock-drag-move', this) this.addEventListener('dock-drag-move', this)
this.addEventListener('dock-drag-end', this) this.addEventListener('dock-drag-end', this)
this.parentElement!.addEventListener('animationend', () => { this.parentElement!.addEventListener('animationend', () => {
this.container.changeLayout() this.container.changeLayout()
let containerOrderString = localStorage.getItem('containerOrder')
let dockOrderString = localStorage.getItem('dockOrder')
if (containerOrderString && dockOrderString) {
let containerOrder: {[prop: string]: any}[] =
JSON.parse(containerOrderString)
let dockOrder: {[prop: string]: any}[] = JSON.parse(dockOrderString)
containerOrder.forEach((record) => {
const icon = document.createElement('site-icon')
icon.setAttribute('color', record.color)
icon.name = record.name
this.container.appendContainerChild(icon, {
row: record.row,
column: record.column,
anchorCoordinate: record.anchorCoordinate,
pagination: record.pagination,
folderName: record.folderName,
})
})
dockOrder.forEach((record) => {
const icon = document.createElement('site-icon')
icon.setAttribute('color', record.color)
icon.name = record.name
this.dock.appendContainerChild(icon, record.order)
})
} else {
this.addAppIcon(1, 1) this.addAppIcon(1, 1)
let promise = new Promise((res) => { let promise = new Promise((res) => {
res(undefined) res(undefined)
@ -97,7 +139,9 @@ export class PanelContainer extends LitElement {
}) })
}) })
} }
}
this.container.synchronise() this.container.synchronise()
this.dock.synchronise()
}) })
} }
@ -111,11 +155,15 @@ export class PanelContainer extends LitElement {
handleEvent(evt: CustomEvent) { handleEvent(evt: CustomEvent) {
switch (evt.type) { switch (evt.type) {
case 'out-of-container': case 'out-of-container':
// @ts-ignore
if (!this.prepareToTransfer) return if (!this.prepareToTransfer) return
const outPage = this.container.pages[this.container.pagination]
this.dragInDock = this.dock.dragIn({ this.dragInDock = this.dock.dragIn({
element: this.elementPlaceholder, element: this.elementPlaceholder,
centerX: evt.detail.centerX, centerX: evt.detail.centerX - outPage.offsetLeft,
centerY: evt.detail.centerY, centerY: evt.detail.centerY - outPage.offsetTop,
gridHeight: evt.detail.gridHeight,
gridWidth: evt.detail.gridWidth,
}) })
break break
case 'drag-return-container': case 'drag-return-container':
@ -125,14 +173,17 @@ export class PanelContainer extends LitElement {
case 'out-of-dock': case 'out-of-dock':
this.prepareToTransfer = true this.prepareToTransfer = true
const {target, centerX, centerY} = evt.detail const {centerX, centerY, gridHeight, gridWidth} = evt.detail
this.container._dnd.active = true this.container._dnd.active = true
const page = this.container.pages[this.container.pagination]
this.container.dragIn('delay', { this.container.dragIn('delay', {
element: this.elementPlaceholder, element: this.elementPlaceholder,
centerX, centerX: centerX + page.offsetLeft,
centerY, centerY: centerY + page.offsetTop,
row: 1, row: 1,
column: 1, column: 1,
gridHeight,
gridWidth,
}) })
break break
case 'drag-and-drop': case 'drag-and-drop':
@ -158,24 +209,28 @@ export class PanelContainer extends LitElement {
this.container.removeContainerChild(evt.detail.target) this.container.removeContainerChild(evt.detail.target)
} }
setTimeout(() => { setTimeout(() => {
// this.dock.removeContainerChild(this.elementPlaceholder)
this.dock.synchronise() this.dock.synchronise()
this.container._dnd.active = false this.container._dnd.active = false
}, 10) }, 10)
break break
case 'dock-drag-end': case 'dock-drag-end':
// @ts-ignore
if (!this.prepareToTransfer) return if (!this.prepareToTransfer) return
if (!this.dragInContainer) { if (!this.dragInContainer) {
// 此时拖拽进 container 标志未显示为 true 也不意味着放置失败, // 此时拖拽进 container 标志未显示为 true 也不意味着放置失败,
// 可能是尚未进行尝试,尝试立即放置拖拽中的图标 // 可能是尚未进行尝试,尝试立即放置拖拽中的图标
const {target, centerX, centerY} = evt.detail const {centerX, centerY, gridHeight, gridWidth} = evt.detail
const page = this.container.pages[this.container.pagination]
this.container.dragIn('immediately', { this.container.dragIn('immediately', {
element: this.elementPlaceholder, element: this.elementPlaceholder,
centerX, centerX: centerX + page.offsetLeft,
centerY, centerY: centerY + page.offsetTop,
row: 1, row: 1,
column: 1, column: 1,
gridHeight,
gridWidth,
}) })
} }
@ -186,7 +241,6 @@ export class PanelContainer extends LitElement {
this.container._dnd.child.container.classList.remove('dragging') this.container._dnd.child.container.classList.remove('dragging')
this.container._dnd.child = undefined this.container._dnd.child = undefined
this.container.synchronise() this.container.synchronise()
this.container._dnd.active = false
} }
this.dock.removeContainerChild(evt.detail.target) this.dock.removeContainerChild(evt.detail.target)
@ -196,14 +250,57 @@ export class PanelContainer extends LitElement {
this.dragInContainer = false this.dragInContainer = false
this.prepareToTransfer = false this.prepareToTransfer = false
this.container._dnd.child = null
this.container._dnd.active = false
this.dock.synchronise() this.dock.synchronise()
break break
case 'page-change':
this.pageIndicator.total = this.container.pages.length
this.pageIndicator.index = this.container.pagination + 1
break
case 'pagination-change':
this.pageIndicator.index = this.container.pagination + 1
break
} }
return false return false
} }
@state() test!: string storeAppOrder() {
let containerOrders: {[prop: string]: any} = []
let dockOrders: {[prop: string]: any} = []
let containerChildren = this.container._children
let dockChildren = this.dock._children
containerChildren.forEach((child) => {
const info = {
name: (child.element as SiteIcon).name,
color: (child.element as SiteIcon).color,
row: child.row,
column: child.column,
pagination: child.pagination,
anchorCoordinate: child.anchorCoordinate,
forderName: child.folderName,
}
containerOrders.push(info)
})
dockChildren.forEach((child) => {
const info = {
name: (child.element as SiteIcon).name,
color: (child.element as SiteIcon).color,
order: child.order,
}
dockOrders.push(info)
})
localStorage.setItem('containerOrder', JSON.stringify(containerOrders))
localStorage.setItem('dockOrder', JSON.stringify(dockOrders))
}
@state() @state()
protected styles: {[subModule: string]: string} = new Proxy( protected styles: {[subModule: string]: string} = new Proxy(
{}, {},
@ -258,8 +355,9 @@ export class PanelContainer extends LitElement {
--container-margin-left: 20px; --container-margin-left: 20px;
/** 控制 dock 距离屏幕底部的距离 */ /** 控制 dock 距离屏幕底部的距离 */
--dock-bottom: 10px; --dock-bottom: 10px;
--icon-size: 92px;
} }
star-container { gaia-container {
height: 80vh; height: 80vh;
width: 100vw; width: 100vw;
} }
@ -299,6 +397,17 @@ export class PanelContainer extends LitElement {
border: 1px solid black; border: 1px solid black;
box-sizing: border-box; box-sizing: border-box;
} }
.gaia-container-child::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
display: block;
width: 4px;
height: 2px;
background-color: pink;
}
`, `,
homescreenStyle, homescreenStyle,
] ]

View File

@ -2,7 +2,7 @@ import {css} from 'lit'
export default css` export default css`
/* 图标归位时的动画 */ /* 图标归位时的动画 */
star-container:not(.loading) gaia-container:not(.loading)
.gaia-container-child:not(.added):not(.dragging) { .gaia-container-child:not(.added):not(.dragging) {
transition: transform 0.2s; transition: transform 0.2s;
} }

View File

@ -15,8 +15,9 @@ export default css`
opacity: 0; opacity: 0;
} }
:host() #subtitle { #subtitle {
transition: opacity 0.2s; transition: opacity 0.2s;
display: none;
} }
#image-container { #image-container {

View File

@ -1,4 +1,4 @@
import {html, css, LitElement} from 'lit' import {html, LitElement} from 'lit'
import {customElement, property, query} from 'lit/decorators.js' import {customElement, property, query} from 'lit/decorators.js'
import style from './icon-style' import style from './icon-style'

View File

@ -19,6 +19,7 @@ export class PanelIndicators extends LitElement {
@queryAll('.myindicator') private myindicators!: IndicatorPagePoint @queryAll('.myindicator') private myindicators!: IndicatorPagePoint
handleEvent(evt: Event) { handleEvent(evt: Event) {
this.myindicators
switch (evt.type) { switch (evt.type) {
case 'click': case 'click':
evt.preventDefault() // iOS上不禁止按钮默认行为将会双击按钮强制放缩屏幕 evt.preventDefault() // iOS上不禁止按钮默认行为将会双击按钮强制放缩屏幕