TASK: #103598 - add container
This commit is contained in:
parent
9d6b72000c
commit
5d7ec91fa7
|
@ -8,3 +8,4 @@
|
|||
- add bubble
|
||||
- add indicator-page-point
|
||||
- add blur
|
||||
- add contaienr
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,132 @@
|
|||
type Timer = number | undefined
|
||||
type IndeterHTMLElement = HTMLElement | null
|
||||
export interface ChildElementInfo {
|
||||
pagination: number
|
||||
row: number
|
||||
column: number
|
||||
folderName: string
|
||||
anchorCoordinate: Coordinate
|
||||
callback?: Function
|
||||
}
|
||||
interface ClickInfo {
|
||||
pageX: number
|
||||
pageY: number
|
||||
clientX: number
|
||||
clientY: number
|
||||
translateX?: number
|
||||
translateY?: number
|
||||
}
|
||||
|
||||
interface lastClickInfo extends ClickInfo {
|
||||
timeStamp: number
|
||||
offsetHeight: number
|
||||
offsetWidth: number
|
||||
offsetX: number
|
||||
offsetY: number
|
||||
}
|
||||
|
||||
export interface Coordinate {
|
||||
[key: string]: number[] | null
|
||||
landscape: number[] | null
|
||||
portrait: number[] | null
|
||||
}
|
||||
|
||||
export enum STATUS {
|
||||
STATIC = 0, // 静置
|
||||
SWIPE = 1 << 1, // 划动
|
||||
DRAG = 1 << 2, // 拖动元素
|
||||
TURN = 1 << 3, // 翻页
|
||||
SORT = 1 << 4, // 整理
|
||||
OPEN_FORDER = 1 << 5, // 开启文件夹状态
|
||||
}
|
||||
|
||||
export 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
|
||||
|
||||
// Timeout used to initiate drag-and-drop actions
|
||||
timeout: Timer
|
||||
|
||||
// The child that was tapped/clicked
|
||||
child: any
|
||||
|
||||
// Whether a drag is active
|
||||
active: boolean
|
||||
|
||||
// The start point of the drag action
|
||||
start: ClickInfo
|
||||
|
||||
// The last point of the drag action
|
||||
last: lastClickInfo
|
||||
|
||||
// Timeout used to send drag-move events
|
||||
moveTimeout: Timer
|
||||
|
||||
// The last time a move event was fired
|
||||
lastMoveEventTime: number
|
||||
|
||||
// Whether to capture the next click event
|
||||
clickCapture: boolean
|
||||
|
||||
// 下一个兄弟节点,用于跨页移动失败后返回原位置时定位用
|
||||
nextSibling: IndeterHTMLElement
|
||||
previousSibling: IndeterHTMLElement
|
||||
|
||||
// 是否正在跨页
|
||||
isSpanning: boolean
|
||||
|
||||
// 正被悬浮覆盖的元素
|
||||
dropTarget: IndeterHTMLElement
|
||||
|
||||
// 最后一个悬浮经过的元素
|
||||
lastDropChild: any
|
||||
pagination: number
|
||||
top: number
|
||||
left: number
|
||||
}
|
||||
|
||||
export interface Container extends HTMLElement {
|
||||
name: string
|
||||
row: number
|
||||
column: number
|
||||
_frozen: boolean
|
||||
_pendingStateChanges: Function[]
|
||||
_children: any[]
|
||||
_dnd: DragAndDrop
|
||||
// 当前所显示的页面页码
|
||||
pagination: number
|
||||
// 组件高度,也是页面高度,用于子组件越界判断
|
||||
height: number
|
||||
width: number
|
||||
// 页面列表
|
||||
pages: any
|
||||
// 滑动偏移量
|
||||
_offsetX: number
|
||||
// 本次触摸滑动距离
|
||||
distance: number
|
||||
// 是否进入整理模式
|
||||
_sortMode: boolean
|
||||
// 状态
|
||||
_status: typeof STATUS[keyof typeof STATUS]
|
||||
// 交换元素位置时,无法进行交换的元素
|
||||
_staticElements: HTMLElement[]
|
||||
// 用于首尾页划动的橡皮绳效果
|
||||
ratio: number
|
||||
// 合并成文件夹计时器
|
||||
mergeTimer: number | undefined
|
||||
// 文件夹
|
||||
folders: {[folderName: string]: any}
|
||||
pageHeight: number
|
||||
pageWidth: number
|
||||
gridWidth: number
|
||||
gridHeight: number
|
||||
status: number
|
||||
openedFolder: any
|
||||
|
||||
reorderChild: Function
|
||||
getChildByElement: Function
|
||||
}
|
|
@ -0,0 +1,295 @@
|
|||
import GaiaContainer from './container'
|
||||
import {Coordinate} from './contianer-interface'
|
||||
/**
|
||||
* Grid 布局
|
||||
* 组件属性:
|
||||
* master: 占位元素,也用于定位静置时的组件实际位置
|
||||
* container: 显示元素,用于放置组件内容——element,也用于拖动时显示组件拖动位置
|
||||
* element: 组件实际内容,通过构造传参传入
|
||||
*
|
||||
* 考虑:
|
||||
* 1. 当为小组件时,插入 container 后需要锚固
|
||||
* 2. 锚固分 landscape 和 portrait 两种方向
|
||||
* 3. TBD:注意,需要考虑旋转屏幕后组件位置冲突问题,当旋转到某一方向时需要越界判断,越界
|
||||
* 时解除所有组件锚固状态重新排列并更新锚固位置
|
||||
*/
|
||||
const defaultCoordinate = {
|
||||
landscape: null,
|
||||
portrait: null,
|
||||
}
|
||||
|
||||
export default class GaiaContainerChild {
|
||||
_element: HTMLElement | null
|
||||
_container: HTMLElement | null = null
|
||||
_master: HTMLElement | null = null
|
||||
isWidget: boolean
|
||||
isFolder: boolean = false
|
||||
row: number
|
||||
column: number
|
||||
manager: GaiaContainer
|
||||
_isStatic: boolean | string
|
||||
folderName: string = ''
|
||||
anchorCoordinate: Coordinate
|
||||
// 静态位置
|
||||
_lastMasterTop: number | null = null
|
||||
_lastMasterLeft: number | null = null
|
||||
_lastElementOrder: string | null = null
|
||||
_lastElementDisplay: string | null = null
|
||||
_lastElementHeight: number | null = null
|
||||
_lastElementWidth: number | null = null
|
||||
// 状态计时器
|
||||
removed: number | undefined = undefined
|
||||
added: number | undefined = undefined
|
||||
constructor(
|
||||
element: HTMLElement | null,
|
||||
row: number = 1,
|
||||
column: number = 1,
|
||||
anchorCoordinate: Coordinate | null,
|
||||
manager: GaiaContainer
|
||||
) {
|
||||
this._element = element
|
||||
this.isWidget = element?.tagName === 'GAIA-WIDGET'
|
||||
this.row = row
|
||||
this.column = column
|
||||
this.manager = manager
|
||||
this._isStatic = false
|
||||
this.anchorCoordinate = anchorCoordinate ?? defaultCoordinate // 两种屏幕方向的锚固坐标
|
||||
this.markDirty()
|
||||
}
|
||||
|
||||
rotate() {
|
||||
const orientation = screen.orientation.type.split('-')[0]
|
||||
|
||||
// 如果没有锚固坐标,则先解除锚固以自适应
|
||||
if (this.anchorCoordinate[orientation]) {
|
||||
this.isStatic = false
|
||||
|
||||
this.setArea()
|
||||
}
|
||||
// this.synchroniseMaster();
|
||||
this.isStatic = true
|
||||
}
|
||||
|
||||
get pagination() {
|
||||
for (let i = 0; i < this.manager.pages.length; i++) {
|
||||
if (this.manager.pages[i].compareDocumentPosition(this.master) & 16) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Can not find pagination`)
|
||||
}
|
||||
|
||||
get position() {
|
||||
if (!this.master.parentElement) return 'unsettled'
|
||||
|
||||
const inPage = this.master.parentElement.classList.contains(
|
||||
'gaia-container-page'
|
||||
)
|
||||
return inPage ? 'page' : 'folder'
|
||||
}
|
||||
|
||||
get element() {
|
||||
return this._element
|
||||
}
|
||||
|
||||
get isStatic() {
|
||||
return this._isStatic
|
||||
}
|
||||
|
||||
set isStatic(value) {
|
||||
!!value ? this.anchor() : this.loosen()
|
||||
if (!this._isStatic && value) {
|
||||
;(this.element as HTMLElement).dispatchEvent(
|
||||
new CustomEvent('anchor', {
|
||||
detail: {
|
||||
anchorCoordinate: this.anchorCoordinate,
|
||||
},
|
||||
})
|
||||
)
|
||||
}
|
||||
this._isStatic = !!value
|
||||
}
|
||||
|
||||
// 是否是页面最后一个组件
|
||||
get isTail() {
|
||||
const page = this.manager.pages[this.pagination]
|
||||
return page.lastChild === this.master
|
||||
}
|
||||
|
||||
/**
|
||||
* 按记录的锚固坐标,将元素锚固到 Grid 网格中
|
||||
*/
|
||||
anchor(type = 'recorder') {
|
||||
const area = this.getArea(type)
|
||||
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}`
|
||||
;(this._element as HTMLElement).dataset.static = type
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取元素所在 Grid 网格区域
|
||||
* @param {String} type 以何种方式获取网格区域: recorder 从记录中,current 从目前位置
|
||||
*/
|
||||
getArea(type = 'recorder') {
|
||||
const orientation = screen.orientation.type.split('-')[0]
|
||||
|
||||
if (type === 'recorder' && this.anchorCoordinate[orientation]) {
|
||||
return this.anchorCoordinate[orientation]
|
||||
}
|
||||
|
||||
const unitHeight = this.master.offsetHeight / this.row
|
||||
const unitWidth = this.master.offsetWidth / this.column
|
||||
const offsetTop = Math.abs(this.master.offsetTop)
|
||||
const offsetLeft = Math.abs(
|
||||
this.master.offsetLeft - this.pagination * this.manager.pageHeight
|
||||
)
|
||||
const rowStart = Math.floor(offsetTop / unitHeight) + 1
|
||||
const columnStart = Math.floor(offsetLeft / unitWidth) + 1
|
||||
|
||||
return [rowStart, columnStart]
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置组件在 Grid 布局中的位置
|
||||
* @param {String} type 以何种方式设置网格区域: recorder 从记录中,current 从目前位置
|
||||
*/
|
||||
setArea(type: string = 'recorder') {
|
||||
const orientation = screen.orientation.type.split('-')[0]
|
||||
this.anchorCoordinate[orientation] = this.getArea(type)
|
||||
return this.anchorCoordinate[orientation]
|
||||
}
|
||||
|
||||
/**
|
||||
* 解除元素的锚固
|
||||
*/
|
||||
loosen() {
|
||||
const orientation = screen.orientation.type.split('-')[0]
|
||||
this.anchorCoordinate[orientation] = null
|
||||
this.master.style.gridArea = 'unset'
|
||||
this.master.style.gridRowStart = `span ${this.row}`
|
||||
this.master.style.gridColumnStart = `span ${this.column}`
|
||||
;(this._element as HTMLElement).dataset.static = String(false)
|
||||
}
|
||||
|
||||
/**
|
||||
* The element that will contain the child element and control its position.
|
||||
*/
|
||||
get container() {
|
||||
if (!this._container) {
|
||||
// Create container
|
||||
let container = document.createElement('div')
|
||||
container.classList.add('gaia-container-child')
|
||||
container.style.position = 'absolute'
|
||||
container.style.top = '0'
|
||||
container.style.left = '0'
|
||||
|
||||
// container.style.height = height + 'px';
|
||||
// container.style.width = width + 'px';
|
||||
container.style.setProperty('--columns', String(this.column))
|
||||
container.style.setProperty('--rows', String(this.row))
|
||||
|
||||
container.appendChild(this._element as HTMLElement) //this.element是div.icon-container
|
||||
|
||||
this._container = container
|
||||
}
|
||||
return this._container
|
||||
}
|
||||
|
||||
changeSize(container = this.container) {
|
||||
const {height, width} = this.master.getBoundingClientRect()
|
||||
container.style.height = height + 'px'
|
||||
container.style.width = width + 'px'
|
||||
this.synchroniseContainer()
|
||||
}
|
||||
|
||||
/**
|
||||
* The element that will be added to the container that will
|
||||
* control the element's transform.
|
||||
*/
|
||||
get master() {
|
||||
if (!this._master) {
|
||||
// Create master
|
||||
let master = document.createElement('div')
|
||||
master.style.gridRowStart = `span ${this.row}`
|
||||
master.style.gridColumnStart = `span ${this.column}`
|
||||
master.style.height = '100%'
|
||||
master.style.width = '100%'
|
||||
master.className = 'container-master'
|
||||
master.appendChild(this.container)
|
||||
this._master = master
|
||||
}
|
||||
return this._master
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears any cached style properties. To be used if elements are
|
||||
* manipulated outside of the methods of this object.
|
||||
*/
|
||||
markDirty() {
|
||||
this._lastElementWidth = null
|
||||
this._lastElementHeight = null
|
||||
this._lastElementDisplay = null
|
||||
this._lastElementOrder = null
|
||||
this._lastMasterTop = null
|
||||
this._lastMasterLeft = null
|
||||
}
|
||||
|
||||
get pageOffsetX() {
|
||||
if (!this.master.parentElement) return 0
|
||||
return this.master.parentElement.offsetLeft
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronise the size of the master with the managed child element.
|
||||
*/
|
||||
synchroniseMaster() {
|
||||
let master = this.master
|
||||
let element = this.element
|
||||
|
||||
let style = window.getComputedStyle(element as HTMLElement)
|
||||
let display = style.display
|
||||
let order = style.order
|
||||
let width = (element as HTMLElement).offsetWidth
|
||||
let height = (element as HTMLElement).offsetHeight
|
||||
|
||||
if (
|
||||
this._lastElementWidth !== width ||
|
||||
this._lastElementHeight !== height ||
|
||||
this._lastElementDisplay !== display ||
|
||||
this._lastElementOrder !== order
|
||||
) {
|
||||
this._lastElementWidth = width
|
||||
this._lastElementHeight = height
|
||||
this._lastElementDisplay = display
|
||||
this._lastElementOrder = order
|
||||
|
||||
// master.style.width = width + "px";
|
||||
// master.style.height = height + "px";
|
||||
master.style.display = display
|
||||
master.style.order = order
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronise the container's transform with the position of the master.
|
||||
*/
|
||||
synchroniseContainer(isActive = false) {
|
||||
let master = this.master
|
||||
let container = this.container
|
||||
let top = master.offsetTop
|
||||
let left = master.offsetLeft
|
||||
|
||||
if (this._lastMasterTop !== top || this._lastMasterLeft !== left) {
|
||||
this._lastMasterTop = top
|
||||
this._lastMasterLeft = left
|
||||
!isActive &&
|
||||
!this.container.classList.contains('dragging') &&
|
||||
(container.style.transform = 'translate(' + left + 'px, ' + top + 'px)')
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,553 @@
|
|||
import GaiaContainerChild from './gaia-container-child'
|
||||
import GaiaContainer from './container'
|
||||
|
||||
/**
|
||||
* 主屏文件夹,只允许 App 图标进入,且文件夹内图标数量大于2时才稳定存在
|
||||
*/
|
||||
|
||||
export default class GaiaContainerFolder extends GaiaContainerChild {
|
||||
// 文件夹名称
|
||||
name: string
|
||||
// 图标 TagName
|
||||
iconName: string = 'gaia-app-icon'
|
||||
// 图标隐藏标题属性
|
||||
hideAttrName = 'hide-subtitle'
|
||||
// 文件夹子节点
|
||||
_children: GaiaContainerChild[] = []
|
||||
isFolder: boolean = true
|
||||
// 文件夹开启状态
|
||||
_status: number = 0
|
||||
// 文件夹处于图标状态的大小
|
||||
folderIconWidth: number = 0
|
||||
// 待添加文件
|
||||
suspendElement: HTMLElement[] = []
|
||||
_id: string
|
||||
// 文件夹名元素
|
||||
_title: HTMLElement | null = null
|
||||
// 开启文件夹动画计时器
|
||||
openTimer: number | undefined = undefined
|
||||
constructor(manager: GaiaContainer, name?: string) {
|
||||
super(null, 1, 1, null, manager)
|
||||
this.name = this.checkAndGetFolderName(name)
|
||||
this._id = `folder-${new Date().getTime()}`
|
||||
this.init()
|
||||
}
|
||||
|
||||
init() {
|
||||
this.addAnimationStyle()
|
||||
this.container.addEventListener('touchstart', this)
|
||||
this.container.addEventListener('touchmove', this)
|
||||
this.container.addEventListener('touchend', this)
|
||||
this.master.className = 'folder initializing'
|
||||
this.master.id = this._id
|
||||
this.master.addEventListener(
|
||||
'animationend',
|
||||
() => {
|
||||
this.master.classList.remove('initializing')
|
||||
// NOTE: 避免同步 synchroniseContainer 产生不必要的动画
|
||||
this.container.style.setProperty('transition', 'unset')
|
||||
this.synchroniseContainer()
|
||||
setTimeout(() => this.container.style.removeProperty('transition'))
|
||||
},
|
||||
{once: true}
|
||||
)
|
||||
this.container.appendChild(this.title)
|
||||
this.container.style.width = this.manager.gridWidth + 'px'
|
||||
// this.manager.injectGlobalCss(this.shadowStyle, this.manager.name, 'gaia-container-folder');
|
||||
}
|
||||
|
||||
get element() {
|
||||
if (
|
||||
!this._element ||
|
||||
!this._element.classList.contains('gaia-container-folder')
|
||||
) {
|
||||
const element = document.createElement('div')
|
||||
|
||||
element.classList.add('gaia-container-folder')
|
||||
this.folderIconWidth = this.manager.gridWidth * 0.6
|
||||
element.style.width = this.folderIconWidth + 'px'
|
||||
element.style.height = this.folderIconWidth + 'px'
|
||||
|
||||
this._element = element
|
||||
}
|
||||
return this._element
|
||||
}
|
||||
|
||||
get title() {
|
||||
if (!this._title || this._title.innerHTML !== this.name) {
|
||||
this._title?.remove()
|
||||
this._title = document.createElement('div')
|
||||
this._title.innerHTML = this.name
|
||||
this._title.classList.add('folder-title')
|
||||
this.container.appendChild(this._title)
|
||||
}
|
||||
return this._title
|
||||
}
|
||||
|
||||
get container() {
|
||||
if (!this._container) {
|
||||
// Create container
|
||||
let container = document.createElement('div')
|
||||
container.classList.add('gaia-container-child')
|
||||
container.style.position = 'absolute'
|
||||
container.style.top = '0'
|
||||
container.style.left = '0'
|
||||
|
||||
container.style.height = this.manager.gridHeight + 'px'
|
||||
container.style.width = this.manager.gridWidth + 'px'
|
||||
|
||||
container.appendChild(this.element) //this.element是div.icon-container
|
||||
|
||||
this._container = container
|
||||
this.master.appendChild(container)
|
||||
}
|
||||
return this._container
|
||||
}
|
||||
|
||||
get children() {
|
||||
return this._children.map((child) => child.element)
|
||||
}
|
||||
|
||||
showIconsSubtitle(element: HTMLElement) {
|
||||
const icon = element.querySelector(this.iconName)
|
||||
icon &&
|
||||
icon.attributes.hasOwnProperty(this.hideAttrName) &&
|
||||
icon.attributes.removeNamedItem(this.hideAttrName)
|
||||
}
|
||||
|
||||
hideIconsSubtitle(element: HTMLElement) {
|
||||
const icon = element.querySelector(this.iconName)
|
||||
const attr = document.createAttribute(this.hideAttrName)
|
||||
icon && icon.attributes.setNamedItem(attr)
|
||||
}
|
||||
|
||||
/**
|
||||
* 将子节点从容器组件的节点表中挪到该文件夹的节点表中
|
||||
* @param {HTMLElement} element master
|
||||
*/
|
||||
movein(element: HTMLElement) {
|
||||
let target
|
||||
let targetIndex
|
||||
this.manager._children.forEach((child: GaiaContainerChild, i) => {
|
||||
if (child.master == element) {
|
||||
target = child
|
||||
targetIndex = i
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
if (!this.master.parentElement && target && targetIndex) {
|
||||
// 无父节点即处于文件夹创建阶段,此时文件夹要代替
|
||||
// element 在容器组件子节点表中的位置
|
||||
this.manager.reorderChild(
|
||||
this.element,
|
||||
(target as GaiaContainerChild).element!
|
||||
)
|
||||
targetIndex++
|
||||
}
|
||||
|
||||
typeof targetIndex == 'number' &&
|
||||
this.manager._children.splice(targetIndex, 1)
|
||||
}
|
||||
|
||||
addAppIcon(element: HTMLElement, shouldOpen = false) {
|
||||
const child = this.manager.getChildByElement(element)
|
||||
|
||||
this._children.push(child!)
|
||||
if (!this._status) {
|
||||
this.hideIconsSubtitle(element)
|
||||
} else {
|
||||
this.showIconsSubtitle(element)
|
||||
}
|
||||
|
||||
if (!this._status && shouldOpen) {
|
||||
this.suspendElement.push(element)
|
||||
this.open()
|
||||
} else {
|
||||
this.movein(element)
|
||||
this.element.appendChild(element)
|
||||
}
|
||||
child!.folderName = this.name
|
||||
}
|
||||
|
||||
removeAppIcon(node: GaiaContainerChild | HTMLElement) {
|
||||
let removeChild = node
|
||||
if (node instanceof HTMLElement) {
|
||||
this._children = this._children.filter((child) => {
|
||||
if (child.master == node && child.container == node) {
|
||||
removeChild = child
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
if (!removeChild) return null
|
||||
;(removeChild as GaiaContainerChild).folderName = ''
|
||||
this.showIconsSubtitle((removeChild as GaiaContainerChild).container)
|
||||
this.manager._children.push(removeChild as GaiaContainerChild)
|
||||
return removeChild
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查文件夹名是否存在,存在则替换成 ‘新建文件夹n’ 样式的名字,
|
||||
* 未传入文件名,则生成一个不重复的文件夹名
|
||||
*/
|
||||
checkAndGetFolderName(folderName?: string, nth?: number): string {
|
||||
const folders = this.manager.folders
|
||||
if (!folderName) {
|
||||
nth = nth ? ++nth : 1
|
||||
folderName = '新建文件夹'
|
||||
}
|
||||
|
||||
const name = folderName + (nth ? nth : '')
|
||||
if (!folders[name]) {
|
||||
return name
|
||||
}
|
||||
|
||||
return this.checkAndGetFolderName(undefined, nth)
|
||||
}
|
||||
|
||||
/**
|
||||
* 开启文件夹,以父组件大小的形式展示,显示所有图标的 subtitle
|
||||
*/
|
||||
open() {
|
||||
if (this._status) return
|
||||
const self = this
|
||||
this._status = 1
|
||||
this.master.classList.add('openning')
|
||||
this._children.forEach((child) => this.showIconsSubtitle(child.master))
|
||||
this.manager.status |= 16
|
||||
this.manager.openedFolder = this
|
||||
this.container.style.height = '100%'
|
||||
this.container.style.width = '100%'
|
||||
this.master.style.setProperty('z-index', String(10))
|
||||
this.container.style.removeProperty('--grid-height')
|
||||
this.container.style.removeProperty('--grid-width')
|
||||
|
||||
this.element.addEventListener('transitionend', function transitionend(evt) {
|
||||
if (evt.target == self.element && evt.propertyName == 'height') {
|
||||
self._children.forEach((child) => child.synchroniseContainer())
|
||||
}
|
||||
self.container.style.setProperty('--folder-element-left', '0px')
|
||||
self.container.style.setProperty('--folder-element-top', '0px')
|
||||
if (
|
||||
self._children[self._children.length].master.compareDocumentPosition(
|
||||
evt.target as HTMLElement
|
||||
) & 16
|
||||
)
|
||||
return
|
||||
self.openTimer = setTimeout(() => {
|
||||
self.master.classList.remove('openning')
|
||||
self.master.classList.add('opened')
|
||||
let element = self.suspendElement.shift()
|
||||
while (element) {
|
||||
self.movein(element)
|
||||
self.element.appendChild(element)
|
||||
element = self.suspendElement.shift()
|
||||
}
|
||||
}, 200)
|
||||
self.element.removeEventListener('transitionend', transitionend)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭文件夹,以父组件网格单元大小显示,同时作为文件夹销毁的入口,
|
||||
* 当应用图标数量不足时,销毁该文件夹
|
||||
*/
|
||||
close() {
|
||||
if (!this._status) return
|
||||
clearTimeout(this.openTimer)
|
||||
this._children = this._children.filter((child) => {
|
||||
if (child.container.classList.contains('dragging')) {
|
||||
this.removeAppIcon(child)
|
||||
return false
|
||||
} else {
|
||||
this.hideIconsSubtitle(child.master)
|
||||
return true
|
||||
}
|
||||
})
|
||||
this.master.classList.add('closing')
|
||||
this.master.classList.remove('opened')
|
||||
this.manager.status &= ~16
|
||||
this.manager.openedFolder = null
|
||||
this.container.style.height = this.manager.gridHeight + 'px'
|
||||
this.container.style.width = this.manager.gridWidth + 'px'
|
||||
this.element.addEventListener(
|
||||
'transitionend',
|
||||
() => {
|
||||
this._status = 0
|
||||
this._children.forEach((child) => child.synchroniseContainer())
|
||||
this.container.style.setProperty(
|
||||
'--folder-element-left',
|
||||
this.element.offsetLeft + 'px'
|
||||
)
|
||||
this.container.style.setProperty(
|
||||
'--folder-element-top',
|
||||
this.element.offsetTop + 'px'
|
||||
)
|
||||
this.master.style.removeProperty('z-index')
|
||||
this.master.classList.remove('closing')
|
||||
if (this._children.length <= 1) {
|
||||
this.destroy()
|
||||
}
|
||||
},
|
||||
{once: true}
|
||||
)
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this._children.length > 1) {
|
||||
return
|
||||
}
|
||||
|
||||
const {height: originHeight, width: originWidth} =
|
||||
this.element.getBoundingClientRect()
|
||||
const {height: targetHeight, width: targetWidth} =
|
||||
this.element.getBoundingClientRect()
|
||||
|
||||
const child = this._children[0]
|
||||
const master = this._children[0].master
|
||||
const childContainer = master.querySelector(
|
||||
'.gaia-container-child'
|
||||
) as HTMLElement
|
||||
|
||||
this.element.style.height = originHeight + 'px'
|
||||
this.element.style.width = originWidth + 'px'
|
||||
this.element.classList.add('scaling') // 剩下的唯一一个图标放大至原来大小展示
|
||||
this.master.classList.add('destroying') // 文件夹背景缩小消失
|
||||
|
||||
// nextTick,用以配合 originXXX 形成动画
|
||||
setTimeout(() => {
|
||||
this.showIconsSubtitle(master)
|
||||
this.element.style.height = targetHeight + 'px'
|
||||
this.element.style.width = targetWidth + 'px'
|
||||
})
|
||||
this.element.addEventListener(
|
||||
'transitionend',
|
||||
() => {
|
||||
this.master.style.position = 'absolute'
|
||||
;(this.master.parentElement as HTMLElement).insertBefore(
|
||||
master,
|
||||
this.master
|
||||
)
|
||||
child.synchroniseContainer()
|
||||
childContainer.style.transition = 'unset'
|
||||
this.element.classList.remove('scaling')
|
||||
|
||||
this.manager.dispatchEvent(
|
||||
new CustomEvent('folder-destroy', {
|
||||
detail: this,
|
||||
composed: true,
|
||||
})
|
||||
)
|
||||
|
||||
setTimeout(() => childContainer.style.removeProperty('transition'))
|
||||
},
|
||||
{once: true}
|
||||
)
|
||||
}
|
||||
|
||||
handleEvent(evt: TouchEvent) {
|
||||
switch (evt.type) {
|
||||
case 'touchend':
|
||||
if (this._status && evt.target === this.container) {
|
||||
this.close()
|
||||
}
|
||||
case 'touchstart':
|
||||
case 'touchmove':
|
||||
if (
|
||||
this._status &&
|
||||
(evt.target as HTMLElement).tagName !== 'GAIA-APP-ICON'
|
||||
) {
|
||||
evt.preventDefault()
|
||||
evt.stopImmediatePropagation()
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
addAnimationStyle() {
|
||||
const styleArr = document.head.querySelectorAll('style')
|
||||
let styleNode
|
||||
styleArr.forEach((item) => {
|
||||
try {
|
||||
if (item.dataset?.name === 'gaia') {
|
||||
styleNode = item
|
||||
}
|
||||
} catch (error) {}
|
||||
})
|
||||
if (!styleNode) {
|
||||
styleNode = document.createElement('style')
|
||||
styleNode.dataset.name = 'gaia'
|
||||
document.head.appendChild(styleNode)
|
||||
}
|
||||
styleNode.innerHTML += `
|
||||
@keyframes folder-fadein {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
`
|
||||
}
|
||||
|
||||
get shadowStyle() {
|
||||
return `
|
||||
::slotted(.gaia-container-child) {
|
||||
box-sizing: content-box;
|
||||
}
|
||||
::slotted(.gaia-container-folder) {
|
||||
transition: transform 0.2s, box-shadow 0.2s, height 0.2s, width 0.2s !important;
|
||||
}
|
||||
::slotted(.folder:not(.opened)) .gaia-container-container {
|
||||
}
|
||||
::slotted(.gaia-container-folder) {
|
||||
display: grid;
|
||||
position: unset;
|
||||
grid-template-rows: repeat(3, 33.3%);
|
||||
grid-template-columns: repeat(3, 33.3%);
|
||||
grid-auto-flow: row dense;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
box-shadow: 0 0 0px 3px rgba(255,255,255,0.5);
|
||||
border-radius: 5px;
|
||||
transition: transform 0.2s, box-shadow 0.2s, height 0.2s, width 0.2s !important;
|
||||
}
|
||||
|
||||
::slotted(.gaia-container-folder::before) {
|
||||
display: block;
|
||||
position: absolute;
|
||||
content: '';
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
::slotted(.gaia-container-folder) .gaia-container-child {
|
||||
height: 16.5% !important;
|
||||
width: 16.5% !important;
|
||||
}
|
||||
|
||||
::slotted(.gaia-container-folder) gaia-app-icon {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
::slotted(.gaia-container-folder::after) {
|
||||
content: '';
|
||||
z-index: 99;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
pointer-events: all;
|
||||
position: absolute;
|
||||
}
|
||||
::slotted(.folder.opened) .gaia-container-folder::after {
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
::slotted( ) .folder:not(.opened) .gaia-container-folder .gaia-container-child:not(.dragging) {
|
||||
position: unset !important;
|
||||
}
|
||||
::slotted(.folder.openning) .gaia-container-folder {
|
||||
position: raletive;
|
||||
}
|
||||
::slotted(.folder.initializing) .gaia-container-child {
|
||||
position: unset !important;
|
||||
transform: unset !important;
|
||||
transition: unset !important;
|
||||
}
|
||||
|
||||
::slotted(.folder.initializing) {
|
||||
animation: folder-fadein 0.3s cubic-bezier(.08,.82,.17,1);
|
||||
}
|
||||
|
||||
::slotted(.folder.opened) .gaia-container-folder {
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
::slotted(.folder) .container-master {
|
||||
pointer-events: none;
|
||||
}
|
||||
::slotted(.folder.opened) .container-master {
|
||||
pointer-events: unset;
|
||||
}
|
||||
::slotted(.destroying) .gaia-container-folder {
|
||||
display: block;
|
||||
}
|
||||
::slotted(.destroying) .gaia-container-folder::before {
|
||||
transform-origin: bottom right;
|
||||
transform: scale(0);
|
||||
transition: transform 0.5s;
|
||||
}
|
||||
::slotted(.destroying) .folder-title {
|
||||
opacity: 0;
|
||||
}
|
||||
::slotted(.scaling) .gaia-container-child {
|
||||
position: unset !important;
|
||||
}
|
||||
|
||||
::slotted(.folder-title) {
|
||||
font-size: 1rem;
|
||||
color: #fff;
|
||||
line-height: 1rem;
|
||||
margin-top: 3px;
|
||||
opacity: 1;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
::slotted(.folder.opened) .folder-title,
|
||||
::slotted(.folder.openning) .folder-title {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
::slotted(.gaia-container-folder) .gaia-container-child {
|
||||
display: unset !important;
|
||||
}
|
||||
::slotted(.folder.openning) > .gaia-container-child,
|
||||
::slotted(.folder.opened) > .gaia-container-child {
|
||||
left: calc(var(--pagination) * var(--page-width)) !important;
|
||||
transform: translate(0) !important;
|
||||
}
|
||||
|
||||
::slotted(.folder.openning) > .gaia-container-child {
|
||||
transform: translate(0) !important;
|
||||
}
|
||||
|
||||
::slotted(.folder.openning) > .gaia-container-child,
|
||||
::slotted(.folder.closing) > .gaia-container-child {
|
||||
transition: transform 0.2s, left 0.2s, height 0.2s, width 0.2s !important;
|
||||
}
|
||||
|
||||
::slotted(.folder) > .gaia-container-child.dragging {
|
||||
transition: unset !important;
|
||||
}
|
||||
|
||||
::slotted(.folder.opened) .gaia-container-folder,
|
||||
::slotted(.folder.openning) .gaia-container-folder {
|
||||
height: 50% !important;
|
||||
max-height: 500px;
|
||||
width: 50% !important;
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
::slotted(.scaling) {
|
||||
transform-origin: top left;
|
||||
position: unset !important;
|
||||
transition: width 0.5s !important;
|
||||
}
|
||||
|
||||
::slotted(.folder.merging) .folder-title {
|
||||
opacity: 0;
|
||||
}
|
||||
`
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
class GaiaContainerPage {
|
||||
_pages: HTMLElement[] = [] // 存储所有添加进 gaia-container 的页面
|
||||
// 等待被移除的页面,仅在编辑、拖拽时出现,若结束前两种状态时仍然没有子节点,则删除
|
||||
_suspending: HTMLElement | null = null
|
||||
_manager
|
||||
observerCallback: MutationCallback
|
||||
constructor(manager: any) {
|
||||
this._manager = manager
|
||||
|
||||
this._manager.addEventListener('statuschange', () => {
|
||||
// gaia-container 退出拖拽模式,且有待删除页面
|
||||
if (!(this._manager._status & 2) && this._suspending) {
|
||||
this.deletePage(this._suspending)
|
||||
}
|
||||
})
|
||||
|
||||
this.observerCallback = (mutations: MutationRecord[]) => {
|
||||
for (const mutation of mutations) {
|
||||
if (!(mutation.target as HTMLElement).children.length) {
|
||||
const page = mutation.target as HTMLElement
|
||||
const container = page.parentElement as any
|
||||
|
||||
page.classList.add('removed')
|
||||
const callback = () => {
|
||||
if (!container || !page.dataset.page) return
|
||||
if (
|
||||
container.pagination == page.dataset.page &&
|
||||
+page.dataset.page > 0
|
||||
) {
|
||||
container.smoothSlide(this._pages[--container.pagination])
|
||||
}
|
||||
|
||||
if (this.editMode) {
|
||||
this._suspending = page
|
||||
} else {
|
||||
this.deletePage(page)
|
||||
}
|
||||
}
|
||||
container && callback()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let proxy = new Proxy(this, {
|
||||
get: (obj, property) => {
|
||||
if (typeof property == 'string' && +property < obj._pages.length) {
|
||||
return obj._pages[+property]
|
||||
}
|
||||
return (obj as any)[property]
|
||||
},
|
||||
})
|
||||
|
||||
return proxy
|
||||
}
|
||||
|
||||
addPage = () => {
|
||||
const div = document.createElement('div')
|
||||
const pagination = `${this._pages.length}`
|
||||
div.className = `gaia-container-page`
|
||||
div.style.setProperty('--pagination', pagination)
|
||||
this._pages.push(div)
|
||||
div.dataset.page = `${this._pages.length - 1}`
|
||||
this.observe(div)
|
||||
|
||||
return div
|
||||
}
|
||||
|
||||
get editMode() {
|
||||
return this._manager._status & 2 || this._manager._status & 8
|
||||
}
|
||||
|
||||
observe = (page: HTMLElement) => {
|
||||
let observe = new MutationObserver(this.observerCallback)
|
||||
observe.observe(page, {
|
||||
childList: true,
|
||||
subtree: true,
|
||||
})
|
||||
}
|
||||
|
||||
deletePage = (page: HTMLElement) => {
|
||||
if (page.children.length) return
|
||||
let index = this._pages.indexOf(page)
|
||||
|
||||
if (this.editMode && index == this.length - 1) {
|
||||
// 处于拖拽状态时,尾页不被删除
|
||||
this._suspending = page
|
||||
return
|
||||
}
|
||||
delete this._pages[index]
|
||||
if (index > -1) {
|
||||
page?.remove?.()
|
||||
let flag = false
|
||||
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
|
||||
return
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
get length() {
|
||||
return this._pages.length
|
||||
}
|
||||
|
||||
forEach = (callback: Function) => {
|
||||
const paginations = this._pages.length
|
||||
for (let i = 0; i < paginations; i++) {
|
||||
callback(this._pages[i], i, this._pages)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default GaiaContainerPage
|
|
@ -0,0 +1,69 @@
|
|||
class GestureManager {
|
||||
element: HTMLElement
|
||||
touches: Touch[] = []
|
||||
swipeTimer: number | undefined = undefined
|
||||
velocity: number = 0.3
|
||||
duration: number = 10
|
||||
recorder: number = 0
|
||||
swipDirection: string = ''
|
||||
|
||||
constructor(element: HTMLElement) {
|
||||
this.element = element
|
||||
this.listeneTouch()
|
||||
}
|
||||
|
||||
listeneTouch() {
|
||||
this.element.addEventListener('touchstart', this)
|
||||
this.element.addEventListener('touchmove', this)
|
||||
this.element.addEventListener('touchend', this)
|
||||
}
|
||||
|
||||
handleEvent(evt: TouchEvent) {
|
||||
switch (evt.type) {
|
||||
case 'touchstart':
|
||||
Array.prototype.forEach.call(evt.changedTouches, (touch: Touch) => {
|
||||
;(touch as any).timeStamp = evt.timeStamp
|
||||
this.touches[touch.identifier] = touch
|
||||
})
|
||||
break
|
||||
case 'touchmove':
|
||||
this.detectHorizonSwipe(evt)
|
||||
break
|
||||
case 'touchend':
|
||||
Array.prototype.forEach.call(
|
||||
evt.changedTouches,
|
||||
(touch) => delete this.touches[touch.identifier]
|
||||
)
|
||||
this.dispatchGesture()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
detectHorizonSwipe(event: TouchEvent) {
|
||||
const {changedTouches, timeStamp: curTime} = event
|
||||
Array.prototype.forEach.call(changedTouches, (touch) => {
|
||||
const {identifier, pageX: curX} = touch
|
||||
const {pageX: preX, timeStamp: preTime} = this.touches[identifier] as any
|
||||
const velocity = (curX - preX) / (curTime - preTime)
|
||||
this.swipDirection = velocity < 0 ? 'swipeleft' : 'swiperight'
|
||||
this.recorder = velocity
|
||||
|
||||
// TBD:暂停翻页动画,再划动手指时会出现速度计算异常
|
||||
if (Math.abs(velocity) > this.velocity) {
|
||||
clearTimeout(this.swipeTimer)
|
||||
this.swipeTimer = setTimeout(
|
||||
() => (this.swipeTimer = undefined),
|
||||
this.duration
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
dispatchGesture() {
|
||||
if (this.swipeTimer) {
|
||||
this.element.dispatchEvent(new Event(this.swipDirection))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default GestureManager
|
|
@ -0,0 +1,61 @@
|
|||
import {html, css, LitElement, TemplateResult} from 'lit'
|
||||
import {customElement, query, state} from 'lit/decorators.js'
|
||||
import GaiaContainer from '../../../components/grid-container/container'
|
||||
import './icon'
|
||||
|
||||
@customElement('panel-container')
|
||||
export class PanelContainer extends LitElement {
|
||||
container!: GaiaContainer
|
||||
@query('.reset') resetBtn!: HTMLElement
|
||||
createRandomColor() {
|
||||
function randomInt(min: number, max: number) {
|
||||
return Math.floor(Math.random() * (max - min)) + min
|
||||
}
|
||||
return (
|
||||
'rgb(' +
|
||||
randomInt(100, 200) +
|
||||
',' +
|
||||
randomInt(100, 200) +
|
||||
',' +
|
||||
randomInt(100, 200) +
|
||||
')'
|
||||
)
|
||||
}
|
||||
addAppIcon() {
|
||||
;(window as any).panel = this
|
||||
console.log(this.container)
|
||||
console.log('add')
|
||||
const icon = document.createElement('site-icon')
|
||||
|
||||
icon.setAttribute('color', this.createRandomColor())
|
||||
this.container.appendContainerChild(icon)
|
||||
}
|
||||
reset() {
|
||||
console.log('reset')
|
||||
}
|
||||
firstUpdated() {
|
||||
this.container = new GaiaContainer()
|
||||
this.container.setAttribute('dragAndDrop', 'true')
|
||||
this.shadowRoot?.appendChild(this.container)
|
||||
;(window as any).container = this.container
|
||||
}
|
||||
render() {
|
||||
return html`
|
||||
<button class="add" @click=${this.addAppIcon}>添加</button>
|
||||
<button class="reset" @click=${this.reset}>重置</button>
|
||||
`
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
star-container {
|
||||
height: 90vh;
|
||||
width: 100vw;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'panel-container': PanelContainer
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
import {css} from 'lit'
|
||||
|
||||
export default css`
|
||||
:host() {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
#display {
|
||||
--background-color: #fff;
|
||||
width: 20vw;
|
||||
height: 12.5vh;
|
||||
}
|
||||
:host([hide-subtitle]) #subtitle {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
:host() #subtitle {
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
#image-container {
|
||||
position: relative;
|
||||
transition: visibility 0.2s, opacity 0.2s;
|
||||
border: 50%;
|
||||
background-color: var(--background-color);
|
||||
}
|
||||
|
||||
#image-container img {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#image-container.initial-load {
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
#image-container > #spinner {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#image-container.downloading > #spinner {
|
||||
display: block;
|
||||
background-size: contain;
|
||||
animation: rotate 2s infinite linear;
|
||||
}
|
||||
|
||||
#image-container,
|
||||
#image-container > div {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#subtitle {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-align: center;
|
||||
line-height: 1rem;
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
from {
|
||||
transform: rotate(1deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
`
|
|
@ -0,0 +1,54 @@
|
|||
import {html, css, LitElement} from 'lit'
|
||||
import {customElement, property, query} from 'lit/decorators.js'
|
||||
import style from './icon-style'
|
||||
|
||||
let count = 0
|
||||
|
||||
@customElement('site-icon')
|
||||
export default class SiteIcon extends LitElement {
|
||||
static defaultColor = '#fff'
|
||||
_color = '#fff'
|
||||
|
||||
@query('#subtitle') subtitle!: HTMLElement
|
||||
@query('#image-container') imgContainer!: HTMLElement
|
||||
@query('#display') displayImg!: HTMLElement
|
||||
|
||||
set color(value: string) {
|
||||
this._color = value
|
||||
this.style.setProperty('--background-color', value)
|
||||
}
|
||||
@property({
|
||||
hasChanged: (newValue, oldValue) => {
|
||||
return newValue === oldValue
|
||||
},
|
||||
})
|
||||
get color() {
|
||||
return this._color ?? SiteIcon.defaultColor
|
||||
}
|
||||
|
||||
static styles = style
|
||||
changeColor(color: string) {
|
||||
this.style.setProperty('--background-color', color)
|
||||
}
|
||||
firstUpdated() {
|
||||
console.log('firstUpdated', this.displayImg)
|
||||
}
|
||||
render() {
|
||||
this.changeColor(this.color)
|
||||
return html`
|
||||
<div id="image-container">
|
||||
<div id="spinner"></div>
|
||||
<img id="display" />
|
||||
</div>
|
||||
<div>
|
||||
<div dir="auto" id="subtitle">图标${++count}</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'site-icon': SiteIcon
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ import './icon/icon'
|
|||
import './general/general'
|
||||
import './indicators/indicators'
|
||||
import './blur/use-blur'
|
||||
import './container/homescreen-container'
|
||||
|
||||
type SEID = String
|
||||
|
||||
|
@ -124,6 +125,14 @@ export class PanelRoot extends LitElement {
|
|||
href="#blur"
|
||||
></star-li>
|
||||
<hr />
|
||||
<star-li
|
||||
type=${LiType.ICON_LABEL}
|
||||
label="主屏容器"
|
||||
icon="dismiss-keyboard"
|
||||
iconcolor="#eee"
|
||||
href="#container"
|
||||
></star-li>
|
||||
<hr />
|
||||
<star-li
|
||||
type=${LiType.ICON_LABEL}
|
||||
label="插件"
|
||||
|
|
Loading…
Reference in New Issue