diff --git a/CHANGELOG.md b/CHANGELOG.md index 2174ba5..04fccf1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,3 +11,4 @@ - add contaienr - add SlotStyleHandler - edit container folder animation +- optimize strategy of container diff --git a/src/components/grid-container/container-style.ts b/src/components/grid-container/container-style.ts new file mode 100644 index 0000000..e8c8803 --- /dev/null +++ b/src/components/grid-container/container-style.ts @@ -0,0 +1,70 @@ +import {css} from 'lit' + +export default css` + :host { + position: relative; + display: block; + + width: 100%; + height: 100%; + } + :host #container { + width: 100%; + height: 100%; + display: grid; + /* 设置三列,每列33.33%宽,也可以用repeat重复 */ + /* grid-template-columns: 33.33% 33.33% 33.33%; */ + /* grid-template-columns: repeat(3, 33.33%); */ + grid-template-rows: repeat(1, 100%); + grid-template-columns: repeat(auto-fit, 100%); + + /* 间距,格式为 row column,单值时表示行列间距一致 */ + gap: 0px; + /* 排列格式,此处意为先列后行,不在意元素顺序,尽量塞满每列 */ + grid-auto-flow: column dense; + + /* 隐式网格宽度,即 grid-template-rows 属性规定列数外的网格列宽 */ + grid-auto-columns: 100vw; + } + + ::slotted(.gaia-container-page) { + display: grid; + grid-template-columns: repeat(4, 25%); + grid-template-rows: repeat(6, 16.66%); + grid-auto-flow: row dense; + + grid-auto-rows: 33%; + height: 100%; + transform: opacity 0.1s; + } + + ::slotted(.gaia-container-child) { + width: 100%; + } + + ::slotted(.gaia-container-child.dragging) { + z-index: 1; + will-change: transform; + } + + ::slotted(.container-master) { + opacity: 1; + transform: opacity 0.15s; + } + ::slotted(.gaia-container-page) > .container-master.merging { + opacity: 0.5; + } + + ::slotted(.gaia-container-child) { + height: calc(var(--grid-height) * var(--rows, 1)); + width: calc(var(--grid-width) * var(--columns, 1)); + display: flex !important; + flex-direction: column; + align-items: center; + justify-content: center; + } + + ::slotted(.folder) > .gaia-container-child { + transition: transform 0.2s, height 0.2s, width 0.2s; + } +` diff --git a/src/components/grid-container/container.ts b/src/components/grid-container/container.ts index e36ebdf..fd19434 100644 --- a/src/components/grid-container/container.ts +++ b/src/components/grid-container/container.ts @@ -6,6 +6,8 @@ import GaiaContainerPage from './gaia-container-page' import GaiaContainerFolder from './gaia-container-folder' import {DragAndDrop, STATUS, ChildElementInfo} from './contianer-interface' import slotStyleHandler from '../../utils/SlotStyleHandler' +import varStyle from './style-variables' +import containerStyle from './container-style' /** * 想法: * 1. 用 grid 布局的特性排列应用图标(1×1)和小组件(n×m) @@ -179,11 +181,13 @@ class GaiaContainer extends LitElement { openedFolder: GaiaContainerFolder | null = null gesture: GestureManager | null = null dndObserver: MutationObserver | null = null - pageHeight: number = 0 - pageWidth: number = 0 - gridHeight: number = 0 - gridWidth: number = 0 + pageHeight: number = 1 + pageWidth: number = 1 + gridHeight: number = 1 + gridWidth: number = 1 istouching: boolean = false + // 组件坐标 + childCoordinate: {[gridId: number]: GaiaContainerChild}[] = [] constructor(row = 6, column = 4) { super() @@ -196,11 +200,7 @@ class GaiaContainer extends LitElement { } firstUpdated() { - slotStyleHandler.injectGlobalCss( - this, - GaiaContainer.styles.cssText, - this.name - ) + slotStyleHandler.injectGlobalCss(this, containerStyle.cssText, this.name) let dndObserverCallback = () => { if (this._dnd.enabled !== this.dragAndDrop) { this._dnd.enabled = this.dragAndDrop @@ -434,8 +434,8 @@ class GaiaContainer extends LitElement { this.width = width this.pageWidth = this.pages.length ? this.pages[0].offsetWidth : 0 this.pageHeight = this.pages.length ? this.pages[0].offsetHeight : 0 - this.gridHeight = this.pageHeight / this.row - this.gridWidth = this.pageWidth / this.column + this.gridHeight = Math.floor(this.pageHeight / this.row) + this.gridWidth = Math.floor(this.pageWidth / this.column) this.style.setProperty('--page-width', this.pageWidth + 'px') this.style.setProperty('--page-height', this.pageHeight + 'px') @@ -493,6 +493,67 @@ class GaiaContainer extends LitElement { return null } + /** + * 根据 x y 坐标获取指定页的网格 ID + * |---- Page 0 (landscape) -----| + * |----+----+----+----+----+----| + * | 0 | 1 | 2 | 3 | 4 | 5 | + * |----+----+----+----+----+----| + * | 6 | 7 | 8 | 9 | 10 | 11 | + * |----+----+----+----+----+----| ...... + * | 12 | 13 | 14 | 15 | 16 | 17 | + * |----+----+----+----+----+----| + * | 18 | 19 | 20 | 21 | 22 | 23 | + * |----+----+----+----+----+----| + * + * | Page 0 (portrait) | + * |----+----+----+----| + * | 0 | 1 | 2 | 3 | + * |----+----+----+----| + * | 4 | 5 | 6 | 7 | + * |----+----+----+----| ...... + * | 8 | 9 | 10 | 11 | + * |----+----+----+----| + * | 12 | 13 | 14 | 15 | + * |----+----+----+----| + * | 16 | 17 | 18 | 19 | + * |----+----+----+----| + * | 20 | 21 | 22 | 23 | + * |----+----+----+----| + * @param x + * @param y + * @returns + */ + getGridIdByCoordinate( + x: number, + y: number, + pagination: number = this.pagination + ) { + let page = this.pages[pagination] + + x += page.scrollLeft - page.offsetLeft + y += page.scrollTop - page.offsetTop + + const coordinateX = parseInt(String(x / this.gridWidth)) + const coordinateY = parseInt(String(y / this.gridHeight)) + const gridId = coordinateX + coordinateY * this.column + + return gridId + } + + getFolderGridIdByCoordinate(x: number, y: number, pagination: number = 0) { + if (!this.openedFolder || !this.openedFolder._status) return -1 + let page = this.pages[this.openedFolder.pagination] + + x += page.scrollLeft - page.offsetLeft - this.openedFolder._lastMasterLeft! + y += page.scrollTop - page.offsetTop - this.openedFolder._lastMasterTop! + const coordinateX = parseInt(String(x / this.openedFolder.gridWidth)) + const coordinateY = parseInt(String(y / this.openedFolder.gridHeight)) + const gridId = coordinateX + coordinateY * 4 + + return gridId + } + get firstChild() { return this._children.length ? this._children[0].element : null } @@ -765,8 +826,8 @@ class GaiaContainer extends LitElement { if (!this.pageHeight || !this.pageWidth) { this.pageWidth = page.offsetWidth this.pageHeight = page.offsetHeight - this.gridHeight = this.pageHeight / this.row - this.gridWidth = this.pageWidth / this.column + this.gridHeight = Math.floor(this.pageHeight / this.row) + this.gridWidth = Math.floor(this.pageWidth / this.column) this.style.setProperty('--page-width', this.pageWidth + 'px') this.style.setProperty('--page-height', this.pageHeight + 'px') @@ -998,7 +1059,7 @@ class GaiaContainer extends LitElement { throw 'getChildOffsetRect called on unknown child' } - getChildFromPoint(x: number, y: number) { + getChildFromPoint_bak(x: number, y: number) { // 是否与主屏文件夹进行交互中 const interactWithFolder = !!this.openedFolder const {offsetHeight, offsetWidth, offsetX, offsetY} = this._dnd.last @@ -1095,6 +1156,40 @@ class GaiaContainer extends LitElement { } } + getChildFromPoint(x: number, y: number) { + // 是否与主屏文件夹进行交互中 + const interactWithFolder = !!this.openedFolder + let children, child: GaiaContainerChild, childIndex, element + if (interactWithFolder && this.openedFolder) { + childIndex = this.getFolderGridIdByCoordinate(x, y, this.pagination) + children = this.openedFolder?.childCoordinate[this.pagination] + child = children[childIndex] + element = child?.element ?? this.openedFolder.container + } else { + childIndex = this.getGridIdByCoordinate(x, y, this.pagination) + children = this.childCoordinate[this.pagination] + child = children[childIndex] + element = child?.element ?? this.pages[this.pagination] + } + // TODO:还需要考虑跨文件夹移动 + if ( + typeof child?.pagination == 'number' && + child.pagination !== this._dnd.child.pagination + ) { + // 当被选中元素与被移动元素页码不一致时,+ + this._dnd.isSpanning = true + } else if (element.dataset.page != this._dnd.child.pagination) { + // 放置页面与原应用图标页面不一致,属于跨页移动 + this._dnd.isSpanning = true + } + + return { + dropTarget: element, + isPage: !interactWithFolder && !child, + pagination: this.pagination, + } + } + cancelDrag() { if (this._dnd.timeout !== null) { clearTimeout(this._dnd.timeout) @@ -1116,7 +1211,6 @@ class GaiaContainer extends LitElement { this._dnd.clickCapture = true this.dispatchEvent(new CustomEvent('drag-finish')) } - this.synchronise() if (this._dnd.lastDropChild) { this._dnd.lastDropChild.master.classList.remove('merging') @@ -1134,6 +1228,7 @@ class GaiaContainer extends LitElement { this._dnd.child = null this._dnd.isSpanning = false this.status &= ~STATUS.DRAG + this.synchronise() } startDrag() { @@ -1244,7 +1339,7 @@ class GaiaContainer extends LitElement { ) this._dnd.dropTarget = dropTarget // 拖拽元素悬浮页面默认为当前页面 - const suspendingPage = isPage ? dropTarget : this.pages[this.pagination] + const suspendingPage = isPage ? dropTarget! : this.pages[this.pagination] if ( dropTarget === this._dnd.child.element || // 悬浮在自身容器之上 (this._dnd.child.isTail && isPage) @@ -1481,10 +1576,10 @@ class GaiaContainer extends LitElement { // if (!insertBefore && !dropChild.master.nextSibling) { if (operator === 'insertAfter' && dropChild) { this.realInsertAfter(this._dnd.child.master, dropChild.master) - this._staticElements.push(dropChild.element as HTMLElement) + // this._staticElements.push(dropChild.element as HTMLElement) } else if (operator === 'insertBefore' && dropChild) { this.realInsertBefore(this._dnd.child.master, dropChild.master) - this._staticElements.push(dropChild.element as HTMLElement) + // this._staticElements.push(dropChild.element as HTMLElement) } else { // TODO: 此处为合并为文件夹方法 // this.mergeForder(); @@ -1531,7 +1626,7 @@ class GaiaContainer extends LitElement { } handleTransformRatio(x: number) { - if (this._status & STATUS.DRAG) return x + if (this._status & STATUS.DRAG) return 1 const percentage = Math.abs(x) / this.pageHeight this.ratio = 1 @@ -1812,74 +1907,7 @@ class GaiaContainer extends LitElement { } } - static styles = css` - :host { - position: relative; - display: block; - - width: 100%; - height: 100%; - } - :host #container { - width: 100%; - height: 100%; - display: grid; - /* 设置三列,每列33.33%宽,也可以用repeat重复 */ - /* grid-template-columns: 33.33% 33.33% 33.33%; */ - /* grid-template-columns: repeat(3, 33.33%); */ - grid-template-rows: repeat(1, 100%); - grid-template-columns: repeat(auto-fit, 100%); - - /* 间距,格式为 row column,单值时表示行列间距一致 */ - gap: 0px; - /* 排列格式,此处意为先列后行,不在意元素顺序,尽量塞满每列 */ - grid-auto-flow: column dense; - - /* 隐式网格宽度,即 grid-template-rows 属性规定列数外的网格列宽 */ - grid-auto-columns: 100vw; - } - - ::slotted(.gaia-container-page) { - display: grid; - grid-template-columns: repeat(4, 25%); - grid-template-rows: repeat(6, 16.66%); - grid-auto-flow: row dense; - - grid-auto-rows: 33%; - height: 100%; - transform: opacity 0.1s; - } - - ::slotted(.gaia-container-child) { - width: 100%; - } - - ::slotted(.gaia-container-child.dragging) { - z-index: 1; - will-change: transform; - } - - ::slotted(.container-master) { - opacity: 1; - transform: opacity 0.15s; - } - ::slotted(.gaia-container-page) > .container-master.merging { - opacity: 0.5; - } - - ::slotted(.gaia-container-child) { - height: calc(var(--grid-height) * var(--rows, 1)); - width: calc(var(--grid-width) * var(--columns, 1)); - display: flex !important; - flex-direction: column; - align-items: center; - justify-content: center; - } - - ::slotted(.folder) > .gaia-container-child { - transition: transform 0.2s, height 0.2s, width 0.2s; - } - ` + static styles = [containerStyle, varStyle] editStyle(moduleName: string, style: string) { this.shadowStyles[moduleName] = style diff --git a/src/components/grid-container/gaia-container-child.ts b/src/components/grid-container/gaia-container-child.ts index 02b3a6e..9c9cedd 100644 --- a/src/components/grid-container/gaia-container-child.ts +++ b/src/components/grid-container/gaia-container-child.ts @@ -129,6 +129,7 @@ export default class GaiaContainerChild { if (!area) return const [rowStart, columStart] = area + const rowEnd = rowStart + +this.row const columEnd = columStart + +this.column this.master.style.gridArea = `${rowStart} / ${columStart} / ${rowEnd} / ${columEnd}` @@ -287,6 +288,26 @@ export default class GaiaContainerChild { let container = this.container let top = master.offsetTop let left = master.offsetLeft + const position = this.position + + // 左上角的 GridID + const gridId = + position == 'page' + ? this.manager.getGridIdByCoordinate(left, top, this.pagination) + : this.manager.getFolderGridIdByCoordinate(left, top, this.pagination) + + for (let i = 0; i < this.row; i++) { + for (let j = 0; j < this.column; j++) { + if (position == 'page') { + this.manager.childCoordinate[this.pagination][gridId + i + j] = this + } else { + // TODO:文件夹分页 + this.manager.folders[this.folderName].childCoordinate[0][ + gridId + i + j + ] = this + } + } + } if (this._lastMasterTop !== top || this._lastMasterLeft !== left) { this._lastMasterTop = top diff --git a/src/components/grid-container/gaia-container-folder.ts b/src/components/grid-container/gaia-container-folder.ts index 5bada67..d571461 100644 --- a/src/components/grid-container/gaia-container-folder.ts +++ b/src/components/grid-container/gaia-container-folder.ts @@ -26,6 +26,13 @@ export default class GaiaContainerFolder extends GaiaContainerChild { _title: HTMLElement | null = null // 开启文件夹动画计时器 openTimer: number | undefined = undefined + // 子节点坐标 + childCoordinate: {[gridId: number]: GaiaContainerChild}[] = [{}] + gridHeight: number = 0 + gridWidth: number = 0 + + _lastElementTop!: number + _lastElementLeft!: number constructor(manager: GaiaContainer, name?: string) { super(null, 1, 1, null, manager) this.name = this.checkAndGetFolderName(name) @@ -59,6 +66,15 @@ export default class GaiaContainerFolder extends GaiaContainerChild { this.container.style.width = this.manager.gridWidth + 'px' } + resize() { + let element = this.element + this._lastElementTop = element?.offsetTop! + this._lastElementLeft = element?.offsetLeft! + + this.gridHeight = this._children[0]._lastElementHeight! + this.gridWidth = this._children[0]._lastElementWidth! + } + get element() { if ( !this._element || @@ -226,6 +242,11 @@ export default class GaiaContainerFolder extends GaiaContainerChild { openTransition = (evt: TransitionEvent) => { if (evt.target == this.element && evt.propertyName == 'height') { this._children.forEach((child) => child.synchroniseContainer()) + if (!this._lastElementLeft || !this._lastElementTop) { + let element = this.element + this._lastElementTop = element?.offsetTop! + this._lastElementLeft = element?.offsetLeft! + } } if ( @@ -246,6 +267,8 @@ export default class GaiaContainerFolder extends GaiaContainerChild { this.element.appendChild(element) element = this.suspendElement.shift() } + this.gridHeight = this._children[0]._lastElementHeight! + this.gridWidth = this._children[0]._lastElementWidth! }) this.element.removeEventListener('transitionend', this.openTransition) @@ -295,6 +318,8 @@ export default class GaiaContainerFolder extends GaiaContainerChild { if (this._children.length <= 1) { this.destroy() } + this.gridHeight = this._children[0]._lastElementHeight! + this.gridWidth = this._children[0]._lastElementWidth! }, {once: true} ) @@ -362,10 +387,10 @@ export default class GaiaContainerFolder extends GaiaContainerChild { case 'touchmove': if ( this._status && - (evt.target as HTMLElement).tagName !== 'GAIA-APP-ICON' + (evt.target as HTMLElement).tagName !== 'SITE-ICON' ) { - // evt.preventDefault() - // evt.stopImmediatePropagation() + evt.preventDefault() + evt.stopImmediatePropagation() } break } diff --git a/src/components/grid-container/gaia-container-page.ts b/src/components/grid-container/gaia-container-page.ts index 28dbed4..9a5bd66 100644 --- a/src/components/grid-container/gaia-container-page.ts +++ b/src/components/grid-container/gaia-container-page.ts @@ -1,10 +1,12 @@ +import GaiaContainer from './container' + class GaiaContainerPage { _pages: HTMLElement[] = [] // 存储所有添加进 gaia-container 的页面 // 等待被移除的页面,仅在编辑、拖拽时出现,若结束前两种状态时仍然没有子节点,则删除 _suspending: HTMLElement | null = null - _manager + _manager: GaiaContainer observerCallback: MutationCallback - constructor(manager: any) { + constructor(manager: GaiaContainer) { this._manager = manager this._manager.addEventListener('statuschange', () => { @@ -61,6 +63,7 @@ class GaiaContainerPage { this._pages.push(div) div.dataset.page = `${this._pages.length - 1}` this.observe(div) + this._manager.childCoordinate[this._pages.length - 1] = {} return div } @@ -90,14 +93,17 @@ class GaiaContainerPage { if (index > -1) { page?.remove?.() let flag = false + let coordinates: GaiaContainer['childCoordinate'] = [] this._pages = this._pages.filter((page, i) => { if (flag) { ;(page.dataset.page as any) = --i page.style.setProperty('--pagination', String(i)) } if (i == index) flag = true + coordinates[i] = this._manager.childCoordinate[i - +flag] return }) + this._manager.childCoordinate = coordinates } } diff --git a/src/components/grid-container/style-variables.ts b/src/components/grid-container/style-variables.ts new file mode 100644 index 0000000..70abd19 --- /dev/null +++ b/src/components/grid-container/style-variables.ts @@ -0,0 +1,8 @@ +import {css} from 'lit' + +export default css` + :host { + /* 图标大小 */ + --icon-size: 108px; + } +` diff --git a/src/test/panels/container/icon-style.ts b/src/test/panels/container/icon-style.ts index 71dd248..fe1ec83 100644 --- a/src/test/panels/container/icon-style.ts +++ b/src/test/panels/container/icon-style.ts @@ -5,8 +5,8 @@ export default css` position: var(--width, absolute); top: 50%; left: 50%; - width: 50%; - height: 50%; + width: var(--icon-size, 50%); + height: var(--icon-size, 50%); max-width: var(--width); max-height: var(--width); border-radius: 50%; @@ -62,11 +62,23 @@ export default css` } */ #subtitle { - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + /* 小黑 文字 中 */ + margin-top: 6px; + height: 26px; + + /* 小正文-浅-居中 */ + font-family: 'OPPOSans'; + font-style: normal; + font-weight: 400; + font-size: 20px; + line-height: 26px; + /* identical to box height, or 130% */ text-align: center; - line-height: 1rem; + + /* 字体/ 高亮白 */ + color: #fafafa; + + text-shadow: 0px 2px 4px rgba(0, 0, 0, 0.28); } @keyframes rotate {