Merge branch 'master' of ssh://172.20.184.160:7999/yr/star-web-components
This commit is contained in:
commit
b36e7d8ecc
|
@ -24,3 +24,4 @@
|
|||
- add function for dragging icon into container
|
||||
- add homescreen function for storing apps' order
|
||||
- fix bugs of container
|
||||
- add gauss blur component
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
content="width=device-width, user-scalable=no, initial-scale=1.0"
|
||||
/>
|
||||
<title>Star Web Components</title>
|
||||
<script type="module" src="./start-element.js"></script>
|
||||
<script type="module" src="./star-element.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- main -->
|
||||
|
|
|
@ -21,6 +21,11 @@
|
|||
"description": "StarWeb组件"
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
"settings": {
|
||||
"access": "readwrite"
|
||||
}
|
||||
},
|
||||
"core": true,
|
||||
"version": "0.0.1"
|
||||
}
|
||||
|
|
|
@ -0,0 +1,192 @@
|
|||
import {html, css, LitElement, HTMLTemplateResult} from 'lit'
|
||||
import {customElement, property, query} from 'lit/decorators.js'
|
||||
@customElement('brightness-slider')
|
||||
export class BrightnessSlider extends LitElement {
|
||||
@query('.progress') progress!: HTMLDivElement
|
||||
@query('.sliderBar') sliderBar!: HTMLDivElement
|
||||
@property({type: String}) id = ''
|
||||
@property({type: String}) _coverLen = ''
|
||||
@property({type: Number}) innerWidth = 0
|
||||
@property({type: Number}) distance = 0
|
||||
@property({type: Number}) currentDistance = 0
|
||||
@property({type: Number}) barWidth = 0
|
||||
@property({type: Number}) max = 100
|
||||
@property({type: Number}) value = 1
|
||||
@property() touchAction = {
|
||||
// 触摸开始落点
|
||||
start: {
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
clientX: 0,
|
||||
clientY: 0,
|
||||
},
|
||||
|
||||
// 触摸结束落点
|
||||
last: {
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
clientX: 0,
|
||||
clientY: 0,
|
||||
},
|
||||
}
|
||||
|
||||
@property({type: String})
|
||||
get coverLen() {
|
||||
return this._coverLen
|
||||
}
|
||||
|
||||
set coverLen(value: string) {
|
||||
this.style.setProperty('--cover-length', value)
|
||||
this._coverLen = value
|
||||
}
|
||||
|
||||
render(): HTMLTemplateResult {
|
||||
return html`
|
||||
<div
|
||||
class="sliderBar"
|
||||
data-icon="brightness"
|
||||
value=${this.value}
|
||||
max=${this.max}
|
||||
@touchstart=${this}
|
||||
@touchmove=${this}
|
||||
@touchend=${this}
|
||||
>
|
||||
<div class="progress"></div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
dislay: block;
|
||||
--cover-length: 3.78px; // 亮度为1
|
||||
--background-lm: rgba(255, 255, 255, 0.5);
|
||||
--background-dm: rgba(0, 0, 0, 0.15);
|
||||
--progress-background-lm: rgba(255, 255, 255, 0.8);
|
||||
--progress-background-dm: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
.sliderBar {
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
background: var(--background-lm);
|
||||
}
|
||||
|
||||
.progress {
|
||||
width: var(--cover-length);
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
background: var(--progress-background-lm);
|
||||
}
|
||||
|
||||
.sliderBar::before {
|
||||
position: absolute;
|
||||
content: attr(data-icon);
|
||||
font-family: gaia-icons;
|
||||
font-style: normal;
|
||||
text-rendering: optimizelegibility;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 900px) {
|
||||
.sliderBar {
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.progress {
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.sliderBar::before {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
left: 29px;
|
||||
top: 29px;
|
||||
font-size: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 600px) {
|
||||
.sliderBar {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.progress {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.sliderBar::before {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
left: 14px;
|
||||
top: 14px;
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
firstUpdated() {
|
||||
if (this.value) {
|
||||
let len = Math.floor((this.value * this.sliderBar.offsetWidth) / this.max)
|
||||
this.coverLen = len.toString()
|
||||
}
|
||||
}
|
||||
|
||||
handleEvent(event: TouchEvent) {
|
||||
switch (event.type) {
|
||||
case 'touchstart':
|
||||
this.touchAction.start.clientX = event.touches[0].clientX
|
||||
this.barWidth = this.sliderBar.offsetWidth
|
||||
break
|
||||
case 'touchmove':
|
||||
this.touchAction.last.clientX = event.touches[0].clientX
|
||||
this.distance =
|
||||
this.touchAction.last.clientX - this.touchAction.start.clientX
|
||||
this.currentDistance = this.distance + this.progress.offsetWidth
|
||||
|
||||
if (this.currentDistance < 0) {
|
||||
// this.currentDistance = 0;
|
||||
this.currentDistance = this.barWidth / this.max
|
||||
}
|
||||
|
||||
if (this.currentDistance > this.barWidth) {
|
||||
this.currentDistance = this.barWidth
|
||||
}
|
||||
this.progress.style.setProperty('width', this.currentDistance + 'px')
|
||||
break
|
||||
case 'touchend':
|
||||
this.value = Math.floor(
|
||||
(this.currentDistance * this.max) / this.barWidth
|
||||
)
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('brightness-slider-change', {
|
||||
detail: {
|
||||
value: this.value,
|
||||
},
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
})
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
setValue(value: number) {
|
||||
this.value = value
|
||||
let len = Math.floor((value * this.sliderBar.offsetWidth) / this.max)
|
||||
this.coverLen = len.toString()
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'brightness-slider': BrightnessSlider
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export * from './brightness-slider.js'
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"name": "@star-web-components/brightness-slider",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"main": "./index.js",
|
||||
"module": "./index.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"default": "./index.js"
|
||||
},
|
||||
"./index": {
|
||||
"default": "./index.js"
|
||||
},
|
||||
"./brightness-slider.js": {
|
||||
"default": "./brightness-slider.js"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"extends": "../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"rootDir": "../../"
|
||||
},
|
||||
"include": ["*.ts"]
|
||||
}
|
|
@ -102,4 +102,10 @@ export default css`
|
|||
align-items: center;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
/* menubutton */
|
||||
:host(#menuitem) button {
|
||||
font-size: 24px;
|
||||
height: 76px;
|
||||
}
|
||||
`
|
||||
|
|
|
@ -21,11 +21,20 @@ export const sharedStyles: CSSResult = css`
|
|||
height: 12px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
margin-right: 20px;
|
||||
margin: 0 10px;
|
||||
background: #000000;
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
#parent {
|
||||
position: relative;
|
||||
height: 500px;
|
||||
width: 500px;
|
||||
left: 50%;
|
||||
top: 66%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
p {
|
||||
position: absolute;
|
||||
font-family: 'OPPOSans';
|
||||
|
@ -35,9 +44,9 @@ export const sharedStyles: CSSResult = css`
|
|||
}
|
||||
|
||||
#zero {
|
||||
position: absolute;
|
||||
left: 44.15%;
|
||||
top: 70%;
|
||||
position: relative;
|
||||
left: 42.5%;
|
||||
top: 68%;
|
||||
}
|
||||
button {
|
||||
position: relative;
|
||||
|
@ -71,8 +80,11 @@ export const sharedStyles: CSSResult = css`
|
|||
|
||||
#slideUp {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
width: 500px;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
.topText {
|
||||
font-size: 20px;
|
||||
|
@ -83,16 +95,16 @@ export const sharedStyles: CSSResult = css`
|
|||
top: calc(50% - 26.5px / 2 - 168.75px);
|
||||
}
|
||||
.spanContainer {
|
||||
position: absolute;
|
||||
left: 206px;
|
||||
top: 349.5px;
|
||||
position: relative;
|
||||
left: 29%;
|
||||
top: 23%;
|
||||
}
|
||||
.grid {
|
||||
position: absolute;
|
||||
display: grid;
|
||||
grid-template-areas: '1 2 3 ' '4 5 6' '7 8 9' '. 0 .';
|
||||
grid-gap: 20px;
|
||||
margin: 404px 176px;
|
||||
gap: 20px;
|
||||
margin: 0 124px;
|
||||
}
|
||||
|
||||
.cancel,
|
||||
|
@ -106,14 +118,13 @@ export const sharedStyles: CSSResult = css`
|
|||
height: 23.5px;
|
||||
line-height: 23.5px;
|
||||
font-size: 18px;
|
||||
top: 73%;
|
||||
}
|
||||
.cancel {
|
||||
left: calc(50% - 36px / 2 - 90px);
|
||||
top: calc(50% - 23.5px / 2 + 231.75px);
|
||||
left: 27%;
|
||||
}
|
||||
.delete {
|
||||
left: calc(50% - 36px / 2 + 90px);
|
||||
top: calc(50% - 23.5px / 2 + 231.75px);
|
||||
left: 65.5%;
|
||||
}
|
||||
.cancel:hover,
|
||||
.delete:hover {
|
||||
|
@ -162,10 +173,10 @@ export const sharedStyles: CSSResult = css`
|
|||
/*密码输入正确后动效*/
|
||||
@keyframes suctip {
|
||||
0% {
|
||||
transform: translateY(0);
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(-1000px);
|
||||
transform: translate(-50%, -300%);
|
||||
}
|
||||
}
|
||||
`
|
||||
|
|
|
@ -18,7 +18,12 @@ export class StarLockNumber extends LitElement {
|
|||
@query('.topText') topText!: HTMLParagraphElement
|
||||
@query('.screen') screen!: HTMLDivElement
|
||||
|
||||
@property({type: Boolean}) locked = false
|
||||
@property({type: Number}) tried = 0
|
||||
@property({type: Number}) time = 0
|
||||
@property({type: Number}) errors = 0
|
||||
@property({type: Number}) clicks = 0
|
||||
@property({type: Number}) second = 120
|
||||
@property({type: Number}) _number = 0
|
||||
@property({type: Number}) clickNumber = 0
|
||||
@property({type: String}) color = ''
|
||||
|
@ -26,6 +31,10 @@ export class StarLockNumber extends LitElement {
|
|||
@property({type: String}) guess = ''
|
||||
@property({type: String}) passwd = '123456'
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div id="slideUp">
|
||||
|
@ -61,11 +70,19 @@ export class StarLockNumber extends LitElement {
|
|||
</div>
|
||||
`
|
||||
}
|
||||
timer() {
|
||||
this.time = window.setInterval(() => {
|
||||
if (--this.second <= 0) {
|
||||
clearInterval(this.time)
|
||||
this.second = 120
|
||||
this.tried = 0
|
||||
}
|
||||
console.log(this.second)
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
touchStart(e: TouchEvent) {
|
||||
e.preventDefault()
|
||||
// console.log('e.target', (e.target as HTMLElement).dataset.num)
|
||||
// console.log('click', this.clickNumber)
|
||||
// 点击数字,圆点变色并输出
|
||||
if ((e.target as Element).tagName === 'BUTTON') {
|
||||
this.clickNumber = Number((e.target as HTMLElement).dataset.num)
|
||||
|
@ -81,7 +98,22 @@ export class StarLockNumber extends LitElement {
|
|||
}
|
||||
//密码错误
|
||||
if (this.clicks == 6 && this.guess !== this.passwd) {
|
||||
console.log('密码错误')
|
||||
//第一次密码错误开始计时120秒
|
||||
if (this.tried == 0) {
|
||||
this.second = 120
|
||||
this.timer()
|
||||
}
|
||||
//输错密码5次后锁定屏幕
|
||||
if (this.tried++ < 4) {
|
||||
this.locked = false
|
||||
console.log('密码错误 已尝试' + this.tried + '次', this.locked)
|
||||
} else {
|
||||
console.log('密码错误 已尝试' + this.tried + '次')
|
||||
this.locked = true
|
||||
clearInterval(this.time)
|
||||
console.log('locked = ' + this.locked + ' 进入已锁定')
|
||||
this.tried = 0
|
||||
}
|
||||
// 抖动反馈
|
||||
for (let i = 0; i < 10; i++) {
|
||||
this.buttons[i].style.setProperty('animation', 'errtips .5s')
|
||||
|
@ -112,7 +144,10 @@ export class StarLockNumber extends LitElement {
|
|||
this.guess = ''
|
||||
this.clicks = 0
|
||||
}
|
||||
//密码正确
|
||||
if (this.guess === this.passwd) {
|
||||
clearInterval(this.time)
|
||||
this.tried = 0
|
||||
for (let j = 0; j < 10; j++) {
|
||||
this.buttons[j].removeAttribute('style')
|
||||
}
|
||||
|
|
|
@ -0,0 +1,150 @@
|
|||
import {html, css, LitElement, HTMLTemplateResult, nothing} from 'lit'
|
||||
import {customElement, property, queryAssignedElements} from 'lit/decorators.js'
|
||||
import {HeaderBarType} from '../header-bar/header-bar.js'
|
||||
import {IconControlBarType} from '../icon-control-bar/icon-control-bar.js'
|
||||
import '../icon-control-bar/icon-control-bar.js'
|
||||
import '../header-bar/header-bar.js'
|
||||
export enum DropDownMenuType {
|
||||
HEADER_WITH_TIME = 'header-with-time',
|
||||
HEADER_WITH_DATE_TIME = 'header-with-date-time',
|
||||
}
|
||||
|
||||
@customElement('drop-down-menu')
|
||||
export class DropDownMenu extends LitElement {
|
||||
@property({type: DropDownMenuType}) type = ''
|
||||
@queryAssignedElements({flatten: true}) slotElements!: HTMLSlotElement[]
|
||||
getOnlyTime(): HTMLTemplateResult {
|
||||
return html`
|
||||
<div class="inner">
|
||||
<header-bar type=${HeaderBarType.ONLY_TIME}>
|
||||
<icon-control-bar
|
||||
id="edit"
|
||||
type=${IconControlBarType.BASE_WITHOUT_BORDER}
|
||||
icon="compose"
|
||||
></icon-control-bar>
|
||||
<icon-control-bar
|
||||
id="settings"
|
||||
type=${IconControlBarType.BASE_WITHOUT_BORDER}
|
||||
icon="settings"
|
||||
></icon-control-bar>
|
||||
</header-bar>
|
||||
<div class="all-quick-icons">
|
||||
<slot @slotchange=${this}></slot>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
getTimeAndDate(): HTMLTemplateResult {
|
||||
return html`
|
||||
<div class="inner">
|
||||
<header-bar type=${HeaderBarType.DATE_TIME}>
|
||||
<icon-control-bar
|
||||
id="settings"
|
||||
type=${IconControlBarType.BASE_WITHOUT_BORDER}
|
||||
icon="settings"
|
||||
></icon-control-bar>
|
||||
</header-bar>
|
||||
<div class="others">
|
||||
<slot @slotchange=${this}></slot>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
render(): HTMLTemplateResult | typeof nothing {
|
||||
switch (this.type) {
|
||||
case DropDownMenuType.HEADER_WITH_TIME:
|
||||
return this.getOnlyTime()
|
||||
case DropDownMenuType.HEADER_WITH_DATE_TIME:
|
||||
return this.getTimeAndDate()
|
||||
default:
|
||||
console.error('unhandled 【pull-down-menu】 type')
|
||||
return nothing
|
||||
}
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
}
|
||||
|
||||
:host([open]) {
|
||||
transform: translateY(0);
|
||||
transition: transform 300ms ease, visibility 300ms;
|
||||
will-change: transform;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.inner {
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 900px) {
|
||||
header-bar[type='only-time'] {
|
||||
dispaly: flex;
|
||||
width: 512px;
|
||||
height: 52px;
|
||||
}
|
||||
|
||||
header-bar[type='date-time'] {
|
||||
dispaly: flex;
|
||||
width: 860px;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
.all-quick-icons {
|
||||
width: 100%;
|
||||
height: calc(100% - 52px);
|
||||
position: relative;
|
||||
top: 40px;
|
||||
}
|
||||
|
||||
.others {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
top: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 600px) {
|
||||
header-bar[type='only-time'] {
|
||||
dispaly: flex;
|
||||
width: 256px;
|
||||
height: 26px;
|
||||
}
|
||||
|
||||
header-bar[type='date-time'] {
|
||||
dispaly: flex;
|
||||
width: 430px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.all-quick-icons {
|
||||
width: 100%;
|
||||
height: calc(100% - 26px);
|
||||
position: relative;
|
||||
top: 20px;
|
||||
}
|
||||
|
||||
.others {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
top: 50px;
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'drop-down-menu': DropDownMenu
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export * from './drop-down-menu.js'
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"name": "@star-web-components/drop-down-menu",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"main": "./index.js",
|
||||
"module": "./index.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"default": "./index.js"
|
||||
},
|
||||
"./index": {
|
||||
"default": "./index.js"
|
||||
},
|
||||
"./drop-down-menu.js": {
|
||||
"default": "./drop-down-menu.js"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"extends": "../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"rootDir": "../../"
|
||||
},
|
||||
"include": [
|
||||
"*.ts",
|
||||
"../icon-control-bar/icon-control-bar.js",
|
||||
"../header-bar/header-bar.js"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
## 高斯模糊小组件
|
||||
|
||||
小组件为解决用 `Css` 属性进行高斯模糊后,浏览器卡顿的问题。原理是用一个 `canvas` 将根据不同 `sigma` 值进行高斯模糊处理后的图展示出来。
|
||||
|
||||
该组件的模糊算法当 `sigma` 越高时 ,其处理模糊的速度越快,越低时处理速度越慢,然而 `sigma` 越低,模糊程度越小,与原图越类似,可以用降低分辨率的方法替换模糊算法。因此组件设定了一个门槛值 `threshold` ,使用者可以根据机器性能和图片质量选择门槛值的高低。
|
||||
|
||||
因为模糊算法耗时受 `sigma` 值和图片分辨率的影响,无法准确掌控,因此为了不阻塞主线程的渲染工作,模糊算法需要放入 `Web worker` 中运行。
|
||||
|
||||
### 属性
|
||||
|
||||
- `src`: 要高斯模糊的图片 URL
|
||||
- `sigma`: 高斯模糊程度系数,除了第一次传值时不会有模糊渐变,之后传值时图片模糊会呈渐变,如若不需要动画则调用方法 `showImmediately` 并传值
|
||||
- `threshold`: 当 `sigma` 大于该值时,组件采用模糊算法,否则采用降低分辨率的方法模糊图片,默认值为 1
|
||||
- `during`: 模糊渐变的最长时间,单位为 `ms`,默认值为 500
|
||||
- `bezier`: 模糊渐变的贝塞尔系数,接受参数为一个有四个数字元素的数组,默认值为 `[0.19, 1, 0.22, 1]`
|
||||
|
||||
### 使用
|
||||
|
||||
注意,请不要使用跨域图片资源,否则无法转化为 `ImageData` 进行模糊计算
|
||||
|
||||
```html
|
||||
<gauss-canvas src="./test.png" sigma="1"></gauss-canvas>
|
||||
```
|
||||
|
||||
```js
|
||||
import '@star-web-element/gauss'
|
||||
const canvas = document.querySelector('gauss-canvas')
|
||||
|
||||
canvas.addEventListener('click', () => {
|
||||
// 会有模糊渐变
|
||||
canvas.sigma ^= 10
|
||||
})
|
||||
|
||||
canvas.addEventListener('mousedown', handleEvent)
|
||||
canvas.addEventListener('mousemove', handleEvent)
|
||||
canvas.addEventListener('mouseup', handleEvent)
|
||||
|
||||
let mouseData = {
|
||||
start: 0,
|
||||
moveDistance: 0,
|
||||
}
|
||||
function handleEvent(evt) {
|
||||
switch (evt.type) {
|
||||
case 'mousedown':
|
||||
mouseData.start = evt.clientY
|
||||
break
|
||||
case ' mousemove':
|
||||
if (mouseData.start) {
|
||||
mouseData.moveDistance = evt.clientY - mouseData.start
|
||||
const conHeight = canvas.parentElement.offsetHeight
|
||||
const targetSigma = (mouseData.moveDistance / conHeight) * 10
|
||||
// 不会有模糊渐变
|
||||
canvas.showImmediately(targetSigma)
|
||||
}
|
||||
break
|
||||
case 'mouseup':
|
||||
mouseData.start = mouseData.moveDistance = 0
|
||||
break
|
||||
}
|
||||
}
|
||||
```
|
|
@ -0,0 +1,287 @@
|
|||
import {html, css, LitElement} from 'lit'
|
||||
import {customElement, property, query} from 'lit/decorators.js'
|
||||
import workerUrl from './worker'
|
||||
enum ErrorType {
|
||||
IMAGE_LOAD_FAILED = 'Image load failed!',
|
||||
IMAGE_NOT_PREPARED = 'The Image resource is not completely prepared!',
|
||||
IMAGE_CROS_ERROR = 'The Image resource crosses domain!',
|
||||
CANVAS_PUTIMAGEDATA_ERROR = 'Canvas putImageData failed!',
|
||||
}
|
||||
|
||||
const cubic_bezier = (
|
||||
p0: number,
|
||||
p1: number,
|
||||
p2: number,
|
||||
p3: number,
|
||||
t: number
|
||||
): number => {
|
||||
return (
|
||||
p0 * Math.pow(1 - t, 3) +
|
||||
3 * p1 * t * Math.pow(1 - t, 2) +
|
||||
3 * p2 * t * t * (1 - t) +
|
||||
p3 * Math.pow(t, 3)
|
||||
)
|
||||
}
|
||||
|
||||
@customElement('gauss-canvas')
|
||||
export default class GaussCanvas extends LitElement {
|
||||
@query('#display') display!: HTMLCanvasElement
|
||||
@property({type: String})
|
||||
get src() {
|
||||
return this._src
|
||||
}
|
||||
set src(value) {
|
||||
if (value && value !== this._src) {
|
||||
const loadCb = () => {
|
||||
this._src = value
|
||||
}
|
||||
const errCb = () => {
|
||||
this._img.src = this._src
|
||||
this._img.removeEventListener('load', loadCb)
|
||||
}
|
||||
|
||||
this._img.addEventListener('error', errCb, {once: true})
|
||||
this._img.addEventListener('load', loadCb, {once: true})
|
||||
|
||||
this._img.src = value
|
||||
}
|
||||
}
|
||||
|
||||
@property() threshold: number = 1
|
||||
@property() transition: boolean = false
|
||||
|
||||
@property()
|
||||
get sigma() {
|
||||
return this._sigma
|
||||
}
|
||||
set sigma(value) {
|
||||
if (value < 0) {
|
||||
value = 0
|
||||
}
|
||||
if (this._sigma == -1) {
|
||||
this._sigma = Number(value)
|
||||
this.show(value)
|
||||
} else if (value !== this._sigma && this._targetSigma == -1) {
|
||||
this._targetSigma = value
|
||||
this.openAnimation()
|
||||
} else {
|
||||
console.info('changed target sigma')
|
||||
}
|
||||
}
|
||||
|
||||
@property({type: Number}) bezier: [number, number, number, number] = [
|
||||
0.19, 1, 0.22, 1,
|
||||
]
|
||||
@property() during = 500
|
||||
|
||||
readonly _img: HTMLImageElement = document.createElement('img')
|
||||
_loadStatus: boolean = false
|
||||
_imgWidth: number = 0
|
||||
_imgHeight: number = 0
|
||||
|
||||
readonly _trCanvas: HTMLCanvasElement = document.createElement('canvas')
|
||||
readonly _trCtx = this._trCanvas.getContext('2d')!
|
||||
_imageData: ImageData | undefined
|
||||
|
||||
_src: string = ''
|
||||
_ctx!: CanvasRenderingContext2D
|
||||
_sigma: number = -1
|
||||
_targetSigma: number = -1
|
||||
|
||||
_worker: Worker | undefined
|
||||
_id!: number
|
||||
|
||||
disWidth: number = 0
|
||||
disHeight: number = 0
|
||||
|
||||
constructor(url: string) {
|
||||
super()
|
||||
this._img.addEventListener('error', this.loadError)
|
||||
this._img.addEventListener('load', this.loadSuccess)
|
||||
this._id = new Date().getTime()
|
||||
this.src = url
|
||||
}
|
||||
|
||||
loadError = () => {
|
||||
this._loadStatus = false
|
||||
this._imgWidth = this._imgHeight = 0
|
||||
|
||||
console.error(ErrorType.IMAGE_LOAD_FAILED)
|
||||
}
|
||||
|
||||
loadSuccess = () => {
|
||||
this._loadStatus = true
|
||||
this._imgHeight = this._img.height
|
||||
this._imgWidth = this._img.width
|
||||
this.show(this.sigma)
|
||||
}
|
||||
|
||||
drawWorker = (
|
||||
sigma: number,
|
||||
imageData: ImageData,
|
||||
canvas: HTMLCanvasElement,
|
||||
draw: Function
|
||||
) => {
|
||||
if (sigma > this.threshold) {
|
||||
if (!this._worker) {
|
||||
this._worker = new Worker(workerUrl)
|
||||
this._worker.addEventListener('message', (evt) => {
|
||||
draw(evt.data.data, evt.data.id)
|
||||
})
|
||||
}
|
||||
|
||||
this._worker.postMessage({
|
||||
imageData,
|
||||
sigma,
|
||||
id: this._id,
|
||||
height: canvas.height,
|
||||
width: canvas.width,
|
||||
})
|
||||
this.isFormatting = true
|
||||
} else {
|
||||
draw(imageData, this._id)
|
||||
this.cb?.()
|
||||
}
|
||||
}
|
||||
|
||||
cb: Function | undefined
|
||||
isFormatting: boolean = false
|
||||
show = (sigma: number, cb?: Function) => {
|
||||
this.cb = cb
|
||||
return new Promise((res, rej) => {
|
||||
if (!this._loadStatus) {
|
||||
if (!this._img.src.includes(this._src)) {
|
||||
this._img.src = this._src
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (this.isFormatting) {
|
||||
return res(void 0)
|
||||
}
|
||||
let ratio: number = sigma
|
||||
if (sigma == 0) {
|
||||
ratio = 1
|
||||
} else if (sigma < this.threshold) {
|
||||
ratio = 2 * this.threshold - 1
|
||||
}
|
||||
|
||||
const canvas = document.createElement('canvas')
|
||||
const ctx = canvas.getContext('2d')!
|
||||
canvas.height = this._img.height / ratio
|
||||
canvas.width = this._img.width / ratio
|
||||
|
||||
ctx.drawImage(
|
||||
this._img,
|
||||
0,
|
||||
0,
|
||||
this._img.width,
|
||||
this._img.height,
|
||||
0,
|
||||
0,
|
||||
canvas.width,
|
||||
canvas.height
|
||||
)
|
||||
|
||||
const draw = (imageData: ImageData, dataId: number) => {
|
||||
if (dataId == this._id) {
|
||||
if (this.cb) {
|
||||
this.cb?.()
|
||||
}
|
||||
// +100 为了清除拖影
|
||||
this.disHeight = this.display.height = imageData.height /* + 100 */
|
||||
this.disWidth = this.display.width = imageData.width
|
||||
this._ctx.putImageData(imageData, 0, 0)
|
||||
this.isFormatting = false
|
||||
res(void 0)
|
||||
}
|
||||
}
|
||||
try {
|
||||
let imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
|
||||
this.drawWorker(sigma, imageData, canvas, draw)
|
||||
} catch (error: any) {
|
||||
if (error.code == 18) {
|
||||
return rej(ErrorType.IMAGE_CROS_ERROR)
|
||||
}
|
||||
return rej(ErrorType.CANVAS_PUTIMAGEDATA_ERROR)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
showImmediately(sigma: number) {
|
||||
return new Promise((res) => {
|
||||
this._sigma = sigma
|
||||
this.show(sigma).then(res)
|
||||
})
|
||||
}
|
||||
|
||||
_startTime!: number
|
||||
animation: Function | undefined
|
||||
openAnimation() {
|
||||
this._startTime = new Date().getTime()
|
||||
const getCurSigma = () => {
|
||||
const t = (new Date().getTime() - this._startTime) / this.during
|
||||
let ratio = cubic_bezier(...this.bezier, t)
|
||||
|
||||
if (ratio > 0.95) {
|
||||
return this._targetSigma
|
||||
}
|
||||
const result = (this._targetSigma - this._sigma) * ratio + this._sigma
|
||||
|
||||
return result > 0 ? result : 0
|
||||
}
|
||||
let changeSigma = () => {
|
||||
this._sigma = getCurSigma()
|
||||
|
||||
if (this._targetSigma !== this._sigma) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
this.animation = () => {
|
||||
if (changeSigma()) {
|
||||
// console.time(`sigma: ${this._sigma}`)
|
||||
this.show(this._sigma, () => {
|
||||
// console.timeEnd(`sigma: ${this._sigma}`)
|
||||
// @ts-ignore
|
||||
requestAnimationFrame(this.animation!)
|
||||
})
|
||||
} else {
|
||||
this._targetSigma = -1
|
||||
}
|
||||
}
|
||||
|
||||
this.animation()
|
||||
}
|
||||
|
||||
protected firstUpdated() {
|
||||
this._ctx = this.display.getContext('2d')!
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<canvas id="display"></canvas>
|
||||
`
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
}
|
||||
canvas {
|
||||
display: block;
|
||||
margin: auto;
|
||||
height: calc(100%);
|
||||
width: 100%;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'gauss-canvas': GaussCanvas
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
"name": "@star-web-components/gauss",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"main": "./index.js",
|
||||
"module": "./index.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"default": "./index.js"
|
||||
},
|
||||
"./index": {
|
||||
"default": "./index.js"
|
||||
},
|
||||
"./index.js": {
|
||||
"default": "./index.js"
|
||||
},
|
||||
"./icons/index.js": {
|
||||
"default": "./icons/index.js"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
// import {
|
||||
// cubic_bezier,
|
||||
// fastBlur,
|
||||
// genSrcAndDest,
|
||||
// mergeChannels,
|
||||
// genKernelsForGaussian,
|
||||
// _fastBlur,
|
||||
// hFastMotionBlur,
|
||||
// vFastMotionBlur,
|
||||
// } from './utils'
|
||||
// @ts-ignore
|
||||
const workCode = () => {
|
||||
function genKernelsForGaussian(sigma: number, n: number) {
|
||||
const wIdeal = Math.sqrt((12 * Math.pow(sigma, 2)) / n + 1)
|
||||
const sizes = []
|
||||
let wl = Math.floor(wIdeal)
|
||||
|
||||
if (wl % 2 === 0) {
|
||||
wl--
|
||||
}
|
||||
const wu = wl + 2
|
||||
let m =
|
||||
(12 * Math.pow(sigma, 2) - n * Math.pow(wl, 2) - 4 * n * wl - 3 * n) /
|
||||
(-4 * wl - 4)
|
||||
m = Math.round(m)
|
||||
|
||||
for (let i = 0; i < n; i++) {
|
||||
sizes.push(i < m ? wl : wu)
|
||||
}
|
||||
|
||||
return sizes
|
||||
}
|
||||
|
||||
function genSrcAndDest(data: Uint8ClampedArray) {
|
||||
const dataInt8 = new Uint8ClampedArray(data.buffer)
|
||||
const simpleChannelLength = dataInt8.length / 4
|
||||
const r = new Uint8ClampedArray(simpleChannelLength)
|
||||
const g = new Uint8ClampedArray(simpleChannelLength)
|
||||
const b = new Uint8ClampedArray(simpleChannelLength)
|
||||
const a = new Uint8ClampedArray(simpleChannelLength)
|
||||
const _r = new Uint8ClampedArray(simpleChannelLength)
|
||||
const _g = new Uint8ClampedArray(simpleChannelLength)
|
||||
const _b = new Uint8ClampedArray(simpleChannelLength)
|
||||
const _a = new Uint8ClampedArray(simpleChannelLength)
|
||||
for (let i = 0; i < simpleChannelLength; i++) {
|
||||
_r[i] = r[i] = dataInt8[i * 4]
|
||||
_g[i] = g[i] = dataInt8[i * 4 + 1]
|
||||
_b[i] = b[i] = dataInt8[i * 4 + 2]
|
||||
_a[i] = a[i] = dataInt8[i * 4 + 3]
|
||||
}
|
||||
return {src: [r, g, b, a], dest: [_r, _g, _b, _a]}
|
||||
}
|
||||
|
||||
function mergeChannels([r, g, b, a]: Uint8ClampedArray[]) {
|
||||
const simpleChannelLength = r.length
|
||||
const data = new Uint8ClampedArray(simpleChannelLength * 4)
|
||||
for (let i = 0; i < simpleChannelLength; i++) {
|
||||
data[4 * i] = r[i]
|
||||
data[4 * i + 1] = g[i]
|
||||
data[4 * i + 2] = b[i]
|
||||
data[4 * i + 3] = a[i]
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
function hFastMotionBlur(
|
||||
src: Uint8ClampedArray,
|
||||
dest: Uint8ClampedArray,
|
||||
width: number,
|
||||
height: number,
|
||||
radius: number
|
||||
) {
|
||||
for (let i = 0; i < height; i++) {
|
||||
let accumulation = radius * src[i * width]
|
||||
for (let j = 0; j <= radius; j++) {
|
||||
accumulation += src[i * width + j]
|
||||
}
|
||||
|
||||
dest[i * width] = Math.round(accumulation / (2 * radius + 1))
|
||||
|
||||
for (let j = 1; j < width; j++) {
|
||||
const left = Math.max(0, j - radius - 1)
|
||||
const right = Math.min(width - 1, j + radius)
|
||||
accumulation =
|
||||
accumulation + (src[i * width + right] - src[i * width + left])
|
||||
dest[i * width + j] = Math.round(accumulation / (2 * radius + 1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function vFastMotionBlur(
|
||||
src: Uint8ClampedArray,
|
||||
dest: Uint8ClampedArray,
|
||||
width: number,
|
||||
height: number,
|
||||
radius: number
|
||||
) {
|
||||
for (let i = 0; i < width; i++) {
|
||||
let accumulation = radius * src[i]
|
||||
for (let j = 0; j <= radius; j++) {
|
||||
accumulation += src[j * width + i]
|
||||
}
|
||||
|
||||
dest[i] = Math.round(accumulation / (2 * radius + 1))
|
||||
|
||||
for (let j = 1; j < height; j++) {
|
||||
const top = Math.max(0, j - radius - 1)
|
||||
const bottom = Math.min(height - 1, j + radius)
|
||||
accumulation =
|
||||
accumulation + src[bottom * width + i] - src[top * width + i]
|
||||
dest[j * width + i] = Math.round(accumulation / (2 * radius + 1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function _fastBlur(
|
||||
src: Uint8ClampedArray,
|
||||
dest: Uint8ClampedArray,
|
||||
width: number,
|
||||
height: number,
|
||||
radius: number
|
||||
) {
|
||||
hFastMotionBlur(dest, src, width, height, radius)
|
||||
vFastMotionBlur(src, dest, width, height, radius)
|
||||
}
|
||||
|
||||
function fastBlur(
|
||||
src: Uint8ClampedArray,
|
||||
dest: Uint8ClampedArray,
|
||||
width: number,
|
||||
height: number,
|
||||
sigma: number
|
||||
) {
|
||||
const boxes = genKernelsForGaussian(sigma, 3)
|
||||
|
||||
for (let i = 0; i < src.length; i++) {
|
||||
dest[i] = src[i]
|
||||
}
|
||||
|
||||
_fastBlur(src, dest, width, height, (boxes[0] - 1) / 2)
|
||||
_fastBlur(src, dest, width, height, (boxes[1] - 1) / 2)
|
||||
_fastBlur(src, dest, width, height, (boxes[2] - 1) / 2)
|
||||
|
||||
return dest
|
||||
}
|
||||
|
||||
onmessage = (evt) => {
|
||||
postMessage({data: blurCanvas(evt.data), id: evt.data.id})
|
||||
}
|
||||
let blurCanvas = ({
|
||||
imageData,
|
||||
height,
|
||||
width,
|
||||
sigma,
|
||||
}: {
|
||||
imageData: ImageData
|
||||
sigma: number
|
||||
height: number
|
||||
width: number
|
||||
}) => {
|
||||
const {src: srcRgba, dest: destRgba} = genSrcAndDest(imageData.data)
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
fastBlur(srcRgba[i], destRgba[i], width, height, sigma)
|
||||
}
|
||||
const destData = mergeChannels(destRgba)
|
||||
imageData.data.set(destData)
|
||||
return imageData
|
||||
}
|
||||
}
|
||||
|
||||
const transfer = () => {
|
||||
let codeStr: string = ''
|
||||
|
||||
codeStr += `(${workCode.toString()})()`
|
||||
return codeStr
|
||||
}
|
||||
|
||||
let workBlob = new Blob([transfer()])
|
||||
|
||||
export default URL.createObjectURL(workBlob)
|
|
@ -9,6 +9,12 @@
|
|||
|
||||
## 主屏指示器(Home Indicator)
|
||||
|
||||
定义:Home Indicator,为了达成全面屏,苹果移除了实体 Home 键,取而代之的是一条 134 x 5 pt 的虚拟 Home 指示条。参照 Macbook Pro 的 Touch Bar,感觉库克对可以滑动的 Bar 真是有迷之热爱。
|
||||
|
||||
1px \* 3/4 = 1pt
|
||||
|
||||
苹果全面屏底部多出了高度为 34 的 Home Indicator 区域。
|
||||
|
||||
主屏指示器应具备的功能:
|
||||
|
||||
- 锁屏处,向上拨动底部可抓取的主屏指示器随手指拖动向上推动锁屏页,进入主屏页
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import {html, css, CSSResultArray} from 'lit'
|
||||
import {customElement} from 'lit/decorators.js'
|
||||
import GestureDector from '../../lib/gesture/gesture-detector'
|
||||
import {StarBaseElement} from '../base/star-base-element'
|
||||
|
||||
@customElement('home-bar-indicator')
|
||||
|
@ -8,57 +7,12 @@ export class HomeBarIndicator extends StarBaseElement {
|
|||
constructor() {
|
||||
super()
|
||||
this.startGestureDetector()
|
||||
this.updateComplete.then(() => {
|
||||
console.log(this)
|
||||
const log = (e: any) => {
|
||||
console.log(e.type, e.target)
|
||||
}
|
||||
|
||||
/**
|
||||
* Chrome 上调用 e.preventDedault() 可阻止右键菜单弹出,同时不会影响
|
||||
* 正常 touch 事件的分发。
|
||||
*
|
||||
* Firefox 上调用 e.preventDedault() 可阻止右键菜单弹出,但会中断 touch 事件,
|
||||
* 表现为:长按(touchstart),中断,手抬起再按(touchmove)
|
||||
*
|
||||
* 测试长按事件时,请使用终端设备或Chrome
|
||||
*/
|
||||
this.addEventListener(
|
||||
'contextmenu',
|
||||
(e) => {
|
||||
e.stopImmediatePropagation()
|
||||
if (navigator.vendor === 'Google Inc.') {
|
||||
e.preventDefault()
|
||||
}
|
||||
},
|
||||
true
|
||||
)
|
||||
|
||||
/**
|
||||
*
|
||||
* 同时监听 tap 和 doubletap 事件时,注意设置可选参数的次序
|
||||
*
|
||||
*/
|
||||
// this.addEventListener('tap', log, {once:true});
|
||||
// this.addEventListener('doubletap', log);
|
||||
// this.addEventListener('panstart', log);
|
||||
// this.addEventListener('panmove', log);
|
||||
// this.addEventListener('panend', log);
|
||||
// this.addEventListener('swipe', log);
|
||||
// this.addEventListener('holdstart', log);
|
||||
|
||||
let myElement = document.createElement('button')
|
||||
myElement.setAttribute('style', 'height:200px;width:200px;')
|
||||
myElement = GestureDector.embedded(myElement)
|
||||
myElement.addEventListener('tap', log)
|
||||
myElement.addEventListener('doubletap', log, {once: true})
|
||||
myElement.addEventListener('holdstart', log)
|
||||
// console.log(GestureDector)
|
||||
// GestureDector.disembedded(myElement)
|
||||
this.shadowRoot?.appendChild(myElement)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 一块等宽屏幕的区域
|
||||
* @returns
|
||||
*/
|
||||
render() {
|
||||
return html``
|
||||
}
|
||||
|
@ -69,17 +23,11 @@ export class HomeBarIndicator extends StarBaseElement {
|
|||
:host {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
height: 34px; /* 1px * 3/4 = 1pt */
|
||||
background-color: yellow;
|
||||
user-select: none;
|
||||
}
|
||||
a {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: yellow;
|
||||
}
|
||||
`,
|
||||
]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,327 @@
|
|||
import {html, css, LitElement, HTMLTemplateResult, nothing} from 'lit'
|
||||
import {customElement, property, query, state} from 'lit/decorators.js'
|
||||
|
||||
export enum HeaderBarType {
|
||||
ONLY_TIME = 'only-time',
|
||||
DATE_TIME = 'date-time',
|
||||
}
|
||||
|
||||
@customElement('header-bar')
|
||||
export class HeaderBar extends LitElement {
|
||||
@state() updateTimeTimeout!: number
|
||||
@state() updateDateTimeout!: number
|
||||
@property({type: HeaderBarType}) type = ''
|
||||
|
||||
@query('#time') time!: HTMLDivElement
|
||||
@query('#date') date!: HTMLDivElement
|
||||
|
||||
getTime(): HTMLTemplateResult {
|
||||
return html`
|
||||
<div class="time-outer">
|
||||
<div id="time"></div>
|
||||
<div class="time-icons">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
getTimeAndDate(): HTMLTemplateResult {
|
||||
return html`
|
||||
<div class="time-date-outer">
|
||||
<div class="time-date">
|
||||
<div id="time"></div>
|
||||
<div id="date"></div>
|
||||
</div>
|
||||
<div class="time-date-icons">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
render() {
|
||||
switch (this.type) {
|
||||
case HeaderBarType.ONLY_TIME:
|
||||
return this.getTime()
|
||||
case HeaderBarType.DATE_TIME:
|
||||
return this.getTimeAndDate()
|
||||
default:
|
||||
console.error('unhandled 【header-bar】 type')
|
||||
return nothing
|
||||
}
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
--only-time-color-lm: #292929;
|
||||
--only-time-color-dm: #f0f0f0;
|
||||
--time-date-time-color-lm: #404040;
|
||||
--time-date-date-color-lm: #000000;
|
||||
--time-date-time-color-dm: #e0e0e0;
|
||||
--time-date-date-color-dm: #e0e0e0;
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
}
|
||||
|
||||
.time-outer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.time-outer > #time {
|
||||
font-family: OPPOSans;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
color: var(--only-time-color-lm);
|
||||
}
|
||||
|
||||
.time-icons > ::slotted(*) {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.time-date-outer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: end;
|
||||
}
|
||||
|
||||
.time-date {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: end;
|
||||
vertical-align: middle;
|
||||
font-family: 'OPPOSans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.time-date > #time {
|
||||
color: var(--time-date-time-color-lm);
|
||||
}
|
||||
|
||||
#date {
|
||||
position: relative;
|
||||
opacity: 0.6;
|
||||
color: var(--time-date-date-color-lm);
|
||||
mix-blend-mode: normal;
|
||||
}
|
||||
|
||||
.host([deep-mode]) .time-outer > #time {
|
||||
color: var(--only-time-color-dm);
|
||||
}
|
||||
|
||||
.host([deep-mode]) #date {
|
||||
color: var(--time-date-date-color-dm);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 900px) {
|
||||
.time-outer > #time {
|
||||
height: 52px;
|
||||
line-height: 52px;
|
||||
font-size: 52px;
|
||||
}
|
||||
|
||||
.time-icons {
|
||||
display: flex;
|
||||
width: 136px;
|
||||
position: relative;
|
||||
right: 4px;
|
||||
}
|
||||
|
||||
.time-icons > ::slotted(:last-child) {
|
||||
position: relative;
|
||||
left: 40px;
|
||||
}
|
||||
|
||||
.time-date {
|
||||
line-height: 64px;
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
.time-date > #time {
|
||||
font-size: 64px;
|
||||
}
|
||||
|
||||
#date {
|
||||
height: 34px;
|
||||
line-height: 34px;
|
||||
left: 22px;
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
.time-date-icons {
|
||||
position: relative;
|
||||
right: 11px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 600px) {
|
||||
.time-outer > #time {
|
||||
height: 26px;
|
||||
line-height: 26px;
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
.time-icons {
|
||||
display: flex;
|
||||
width: 68px;
|
||||
position: relative;
|
||||
right: 2px;
|
||||
}
|
||||
|
||||
.time-icons > ::slotted(:last-child) {
|
||||
position: relative;
|
||||
left: 20px;
|
||||
}
|
||||
|
||||
.time-date {
|
||||
line-height: 32px;
|
||||
left: 5px;
|
||||
}
|
||||
|
||||
.time-date > #time {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
#date {
|
||||
height: 17px;
|
||||
line-height: 17px;
|
||||
left: 11px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.time-date-icons {
|
||||
position: relative;
|
||||
right: 5.5px;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
firstUpdated() {
|
||||
this.autoUpdateDateTime()
|
||||
}
|
||||
|
||||
autoUpdateDateTime() {
|
||||
switch (this.type) {
|
||||
case HeaderBarType.ONLY_TIME:
|
||||
window.clearTimeout(this.updateTimeTimeout)
|
||||
this.autoUpdateTime()
|
||||
break
|
||||
case HeaderBarType.DATE_TIME:
|
||||
window.clearTimeout(this.updateDateTimeout)
|
||||
window.clearTimeout(this.updateTimeTimeout)
|
||||
this.autoUpdateDate()
|
||||
this.autoUpdateTime()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
autoUpdateTime() {
|
||||
var d = new Date()
|
||||
this.time.innerHTML = this.formatTime(d)
|
||||
let self = this
|
||||
var remainMillisecond = (60 - d.getSeconds()) * 1000
|
||||
this.updateTimeTimeout = window.setTimeout(() => {
|
||||
self.autoUpdateTime()
|
||||
}, remainMillisecond)
|
||||
}
|
||||
|
||||
autoUpdateDate() {
|
||||
var d = new Date()
|
||||
this.date.innerHTML = this.formatDate(d) + ' ' + this.getWeekDay()
|
||||
let remainMillisecond =
|
||||
(24 - d.getHours()) * 3600 * 1000 -
|
||||
d.getMinutes() * 60 * 1000 -
|
||||
d.getMilliseconds()
|
||||
let self = this
|
||||
this.updateDateTimeout = window.setTimeout(function updateDateTimeout() {
|
||||
self.autoUpdateDate()
|
||||
}, remainMillisecond)
|
||||
}
|
||||
|
||||
getWeekDay() {
|
||||
var d = new Date()
|
||||
let daynumber = d.getDay()
|
||||
let day = ''
|
||||
switch (daynumber) {
|
||||
case 0:
|
||||
day = '周日'
|
||||
break
|
||||
case 1:
|
||||
day = '周一'
|
||||
break
|
||||
case 2:
|
||||
day = '周二'
|
||||
break
|
||||
case 3:
|
||||
day = '周三'
|
||||
break
|
||||
case 4:
|
||||
day = '周四'
|
||||
break
|
||||
case 5:
|
||||
day = '周五'
|
||||
break
|
||||
case 6:
|
||||
day = '周六'
|
||||
break
|
||||
}
|
||||
return day
|
||||
}
|
||||
|
||||
formatDate(d: Date | string, iso?: boolean) {
|
||||
if (d instanceof Date) {
|
||||
if (iso) {
|
||||
let date = d.toISOString() // Thu Oct 07 2021 07:45:18 GMT+0800
|
||||
date = date.split('T')[0]
|
||||
return date
|
||||
} else {
|
||||
return d.toLocaleString(navigator.languages as string[], {
|
||||
// year: 'numeric',
|
||||
month: 'long',
|
||||
day: '2-digit',
|
||||
})
|
||||
}
|
||||
} else {
|
||||
return d
|
||||
}
|
||||
}
|
||||
|
||||
formatTime(d: Date | string, iso?: boolean) {
|
||||
if (d instanceof Date) {
|
||||
if (iso) {
|
||||
let time = d.toLocaleTimeString()
|
||||
time = time.split(' ')[0]
|
||||
if (time.indexOf(':') === 1) {
|
||||
time = `0${time}`
|
||||
}
|
||||
return time
|
||||
} else {
|
||||
return d.toLocaleString(navigator.languages as string[], {
|
||||
// hour12: (navigator as any).mozHour12,
|
||||
hour12: false,
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if (d.indexOf(':') == 1) {
|
||||
d = '0' + d
|
||||
}
|
||||
return d
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'header-bar': HeaderBar
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export * from './header-bar.js'
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"name": "@star-web-components/header-bar",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"main": "./index.js",
|
||||
"module": "./index.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"default": "./index.js"
|
||||
},
|
||||
"./index": {
|
||||
"default": "./index.js"
|
||||
},
|
||||
"./header-bar.js": {
|
||||
"default": "./header-bar.js"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"extends": "../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"rootDir": "../../"
|
||||
},
|
||||
"include": ["*.ts"]
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
import {html, css, LitElement, HTMLTemplateResult, nothing} from 'lit'
|
||||
import {customElement, property, queryAssignedElements} from 'lit/decorators.js'
|
||||
|
||||
export enum IconControlBarGroupType {
|
||||
BASE = 'base',
|
||||
WITH_STATE = 'with-state',
|
||||
}
|
||||
|
||||
@customElement('icon-control-bar-group')
|
||||
export class IconControlBarGroup extends LitElement {
|
||||
@property({type: IconControlBarGroupType}) type = ''
|
||||
@property({type: Number}) count = 2
|
||||
@queryAssignedElements({flatten: true}) slotElements!: HTMLSlotElement[]
|
||||
|
||||
getbase(): HTMLTemplateResult {
|
||||
const colorstyle =
|
||||
this.count !== 4
|
||||
? html`
|
||||
<style>
|
||||
.icon-only > div > ::slotted(*) {
|
||||
width: calc(100% / ${this.count}) !important;
|
||||
}
|
||||
</style>
|
||||
`
|
||||
: nothing
|
||||
return html`
|
||||
<div class="icon-only">
|
||||
<div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
${colorstyle}
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
getIconWithState(): HTMLTemplateResult {
|
||||
return html`
|
||||
<div class="icon-with-state">
|
||||
<slot></slot>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
this.count = this.slotElements.length
|
||||
}
|
||||
|
||||
render() {
|
||||
switch (this.type) {
|
||||
case IconControlBarGroupType.BASE:
|
||||
return this.getbase()
|
||||
case IconControlBarGroupType.WITH_STATE:
|
||||
return this.getIconWithState()
|
||||
default:
|
||||
console.error('unhandled 【icon-control-bar-group】 type')
|
||||
return nothing
|
||||
}
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
--background-lm: #ffffff;
|
||||
--background-dm: #000000;
|
||||
--opacity-lm: 0.75;
|
||||
--opacity-dm: 0.25;
|
||||
--line-border-lm: rgba(0, 0, 0, 0.07);
|
||||
--line-border-dm: rgba(255, 255, 255, 0.07);
|
||||
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
background: var(--background-lm);
|
||||
mix-blend-mode: normal;
|
||||
opacity: var(--opacity-lm);
|
||||
}
|
||||
|
||||
.icon-with-state {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
// justify-content: space-around;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.icon-only {
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
background: inherit;
|
||||
border-radius: inherit;
|
||||
opacity: inherit;
|
||||
}
|
||||
|
||||
.icon-only > div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.icon-only > div > ::slotted(*) {
|
||||
width: 25%;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.icon-only > div > ::slotted(:first-child) {
|
||||
border-left-style: none;
|
||||
}
|
||||
|
||||
:host([deep-mode]) {
|
||||
background: var(--background-dm);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 900px) {
|
||||
:host {
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.icon-only > div > ::slotted(*) {
|
||||
height: 30px;
|
||||
border-left: 2px solid var(--line-border-lm);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
:host([deep-mode]) .icon-only > div > ::slotted(*) {
|
||||
border-left: 2px solid var(--line-border-dm);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 600px) {
|
||||
:host {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.icon-only > div > ::slotted(*) {
|
||||
height: 15px;
|
||||
border-left: 1px solid var(--line-border-lm);
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
:host([deep-mode]) .icon-only > div > ::slotted(*) {
|
||||
border-left: 1px solid var(--line-border-dm);
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'icon-control-bar-group': IconControlBarGroup
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export * from './icon-control-bar-group.js'
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"name": "@star-web-components/icon-control-bar-group",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"main": "./index.js",
|
||||
"module": "./index.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"default": "./index.js"
|
||||
},
|
||||
"./index": {
|
||||
"default": "./index.js"
|
||||
},
|
||||
"./icon-control-bar-group.js": {
|
||||
"default": "./icon-control-bar-group.js"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"extends": "../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"rootDir": "../../"
|
||||
},
|
||||
"include": ["*.ts"]
|
||||
}
|
|
@ -0,0 +1,450 @@
|
|||
import {html, css, LitElement, HTMLTemplateResult, nothing} from 'lit'
|
||||
import {
|
||||
customElement,
|
||||
property,
|
||||
eventOptions,
|
||||
query,
|
||||
state,
|
||||
} from 'lit/decorators.js'
|
||||
|
||||
export enum IconControlBarType {
|
||||
BASE = 'base',
|
||||
BASE_WITHOUT_BORDER = 'base-without-border',
|
||||
WITH_STATE = 'with-state',
|
||||
}
|
||||
|
||||
@customElement('icon-control-bar')
|
||||
export class IconControlBar extends LitElement {
|
||||
@property({type: IconControlBarType}) type = ''
|
||||
@property({type: String}) icon = ''
|
||||
@property({type: String}) stateDesc = ''
|
||||
@property({type: String}) settingsKey = ''
|
||||
@property({type: Boolean}) bgchange = false
|
||||
@property({type: String}) id = ''
|
||||
@property({type: Boolean}) active = false
|
||||
@property({type: Boolean}) longPress = false
|
||||
@state({}) timer!: NodeJS.Timeout
|
||||
|
||||
@query('.more-info-icon') moreInfoIcon!: HTMLDivElement
|
||||
@query('.icon-button') iconBtn!: HTMLDivElement
|
||||
|
||||
getbase(): HTMLTemplateResult | typeof nothing {
|
||||
if (!this.icon) {
|
||||
console.error('【icon-control-bar】缺少 icon 参数')
|
||||
return nothing
|
||||
}
|
||||
|
||||
const iconstyle = html`
|
||||
<style>
|
||||
@media screen and (min-width: 900px) {
|
||||
:host {
|
||||
width: 104px;
|
||||
height: 104px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 600px) {
|
||||
:host {
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
`
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="icon-button icon-base with-border"
|
||||
data-icon=${this.icon}
|
||||
@click=${this}
|
||||
@touchstart=${this}
|
||||
@touchend=${this}
|
||||
>
|
||||
${iconstyle}
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
getbaseWithOutBorder(): HTMLTemplateResult | typeof nothing {
|
||||
if (!this.icon) {
|
||||
console.error('【icon-control-bar】缺少 icon 参数')
|
||||
return nothing
|
||||
}
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="icon-button icon-base without-border"
|
||||
data-icon=${this.icon}
|
||||
@click=${this}
|
||||
@touchstart=${this}
|
||||
@touchend=${this}
|
||||
></div>
|
||||
`
|
||||
}
|
||||
|
||||
getwithstate(): HTMLTemplateResult | typeof nothing {
|
||||
if (!this.icon) {
|
||||
console.error('【icon-control-bar】缺少 icon 参数')
|
||||
return nothing
|
||||
}
|
||||
|
||||
const iconstyle = html`
|
||||
<style>
|
||||
@media screen and (min-width: 900px) {
|
||||
:host {
|
||||
width: 240px;
|
||||
height: 108px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 600px) {
|
||||
:host {
|
||||
width: 120px;
|
||||
height: 54px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
`
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="icon-button icon-with-state with-border config-activity"
|
||||
data-icon=${this.icon}
|
||||
@click=${this}
|
||||
@touchstart=${this}
|
||||
@touchend=${this}
|
||||
>
|
||||
<p>${this.stateDesc}</p>
|
||||
<div
|
||||
class="more-info-icon"
|
||||
@click=${this.handleInfo}
|
||||
data-icon="expand-left"
|
||||
></div>
|
||||
${iconstyle}
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
@eventOptions({passive: false})
|
||||
handleEvent(event: Event) {
|
||||
switch (event.type) {
|
||||
case 'click':
|
||||
this.handleClick(event)
|
||||
break
|
||||
case 'touchstart':
|
||||
this.handlePress(event)
|
||||
break
|
||||
case 'touchend':
|
||||
this.handlePressEnd()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
handleClick(event: Event) {
|
||||
if (this.longPress) {
|
||||
return
|
||||
}
|
||||
|
||||
let isActive = true // 闹铃
|
||||
if (this.bgchange) {
|
||||
let target = event.target as HTMLElement
|
||||
if (target.nodeName == 'P') {
|
||||
target = target.parentElement as HTMLElement
|
||||
}
|
||||
|
||||
if (target.className == 'more-info-icon') {
|
||||
target = target.parentElement as HTMLElement
|
||||
}
|
||||
|
||||
isActive = target.classList.contains('active')
|
||||
this.activeOrInactive(isActive, target)
|
||||
}
|
||||
|
||||
// if (this.type == IconControlBarType.WITH_STATE) {
|
||||
// if (target.className == "more-info-icon") {
|
||||
// target = target.parentElement as HTMLElement;
|
||||
// }
|
||||
// if (target.classList.contains("active")) {
|
||||
// this.moreInfoIcon.dataset.icon = "expand-left";
|
||||
// } else {
|
||||
// this.moreInfoIcon.dataset.icon = "";
|
||||
// }
|
||||
// }
|
||||
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('icon-control-bar-click', {
|
||||
detail: {
|
||||
id: this.id ? this.id : this.icon,
|
||||
isActive: isActive,
|
||||
target: this,
|
||||
},
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
composed: true,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
handleInfo() {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('more-info-action', {
|
||||
detail: this.icon,
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
composed: true,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
handlePress(event: Event) {
|
||||
let target = event.target as HTMLElement
|
||||
if (target.nodeName == 'P') {
|
||||
target = target.parentElement as HTMLElement
|
||||
}
|
||||
|
||||
if (this.getAttribute('disabled') === 'true') {
|
||||
return
|
||||
}
|
||||
|
||||
this.timer = setTimeout(() => {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('icon-control-bar-touch-start', {
|
||||
detail: {
|
||||
id: this.id ? this.id : this.icon,
|
||||
target: this,
|
||||
},
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
composed: true,
|
||||
})
|
||||
)
|
||||
this.longPress = true
|
||||
}, 300)
|
||||
}
|
||||
|
||||
handlePressEnd() {
|
||||
clearTimeout(this.timer)
|
||||
if (this.timer && this.longPress) {
|
||||
this.longPress = false
|
||||
;(this.timer as any) = null
|
||||
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('icon-control-bar-touch-end', {
|
||||
detail: {
|
||||
id: this.id ? this.id : this.icon,
|
||||
target: this,
|
||||
},
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
composed: true,
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
activeOrInactive(isActive: boolean, element?: HTMLElement) {
|
||||
if (element == null) {
|
||||
isActive
|
||||
? this.iconBtn.classList.remove('active')
|
||||
: this.iconBtn.classList.add('active')
|
||||
this.active = isActive
|
||||
} else {
|
||||
isActive
|
||||
? element.classList.remove('active')
|
||||
: element.classList.add('active')
|
||||
}
|
||||
}
|
||||
|
||||
render(): HTMLTemplateResult | typeof nothing {
|
||||
switch (this.type) {
|
||||
case IconControlBarType.BASE:
|
||||
return this.getbase()
|
||||
case IconControlBarType.BASE_WITHOUT_BORDER:
|
||||
return this.getbaseWithOutBorder()
|
||||
case IconControlBarType.WITH_STATE:
|
||||
return this.getwithstate()
|
||||
default:
|
||||
console.error('unhandled 【icon-control-bar】 type')
|
||||
return nothing
|
||||
}
|
||||
}
|
||||
|
||||
addClass(className: string[]) {
|
||||
this.iconBtn.classList.add(...className)
|
||||
}
|
||||
|
||||
removeClass(className: string) {
|
||||
this.iconBtn.classList.remove(className)
|
||||
}
|
||||
|
||||
getClassList() {
|
||||
return this.iconBtn.classList
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
--background-active: #1d98f5;
|
||||
--background-lm: rgba(255, 255, 255, 0.35);
|
||||
--background-dm: rgba(0, 0, 0, 0.15);
|
||||
--text-color-lm: #4d4d4d;
|
||||
--text-color-dm: #d1d1d1;
|
||||
--text-color-active: #ffffff;
|
||||
}
|
||||
|
||||
.active {
|
||||
background-color: var(--background-active) !important;
|
||||
color: var(--text-color-active) !important;
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
.with-border {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: var(--background-lm);
|
||||
}
|
||||
|
||||
.icon-button::before {
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
content: attr(data-icon);
|
||||
font-family: gaia-icons;
|
||||
font-style: normal;
|
||||
text-rendering: optimizelegibility;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.icon-base {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.more-info-icon::after {
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
content: attr(data-icon);
|
||||
font-family: gaia-icons;
|
||||
font-style: normal;
|
||||
text-rendering: optimizelegibility;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
p {
|
||||
position: relative;
|
||||
color: var(--text-color-lm);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-family: OPPOSans;
|
||||
font-style: normal;
|
||||
mix-blend-mode: normal;
|
||||
}
|
||||
|
||||
.active > p {
|
||||
color: var(--text-color-active);
|
||||
}
|
||||
|
||||
:host([deep-mode]) .with-border {
|
||||
background: var(--background-dm);
|
||||
}
|
||||
|
||||
:host([deep-mode]) p {
|
||||
position: relative;
|
||||
color: var(--text-color-dm);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-family: OPPOSans;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
mix-blend-mode: normal;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 600px) {
|
||||
.with-border {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.icon-button::before {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.icon-with-state::before {
|
||||
position: relative;
|
||||
left: 11px;
|
||||
}
|
||||
|
||||
.more-info-icon {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
position: relative;
|
||||
left: 29px;
|
||||
}
|
||||
|
||||
.more-info-icon::after {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
line-height: 8px;
|
||||
font-size: 8px;
|
||||
}
|
||||
|
||||
p {
|
||||
width: 50px;
|
||||
height: 10px;
|
||||
left: 17px;
|
||||
font-weight: 400;
|
||||
font-size: 10.5px;
|
||||
line-height: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 900px) {
|
||||
.with-border {
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.icon-button::before {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
line-height: 48px;
|
||||
font-size: 48px;
|
||||
}
|
||||
|
||||
.icon-with-state::before {
|
||||
position: relative;
|
||||
left: 22px;
|
||||
}
|
||||
|
||||
.more-info-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
position: relative;
|
||||
left: 58px;
|
||||
}
|
||||
|
||||
.more-info-icon::after {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
line-height: 16px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
p {
|
||||
width: 100px;
|
||||
height: 20px;
|
||||
left: 34px;
|
||||
font-size: 21px;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'icon-control-bar': IconControlBar
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export * from './icon-control-bar.js'
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"name": "@star-web-components/icon-control-bar",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"main": "./index.js",
|
||||
"module": "./index.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"default": "./index.js"
|
||||
},
|
||||
"./index": {
|
||||
"default": "./index.js"
|
||||
},
|
||||
"./icon-control-bar.js": {
|
||||
"default": "./icon-control-bar.js"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"extends": "../../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"rootDir": "../../"
|
||||
},
|
||||
"include": ["*.ts"]
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
import {html, css, LitElement, PropertyValueMap, svg} from 'lit'
|
||||
import {customElement, property} from 'lit/decorators.js'
|
||||
|
||||
@customElement('indicator-page-deformation')
|
||||
export class IndicatorPageDeformation extends LitElement {
|
||||
@property({type: Number, reflect: true}) total = 1
|
||||
@property({type: Number, reflect: true}) index = 1
|
||||
@property({type: Boolean, reflect: true}) edit = false
|
||||
@property({type: Boolean, reflect: true}) column = false
|
||||
|
||||
#firstRender = true
|
||||
|
||||
protected shouldUpdate(_changedProperties: PropertyValueMap<this>): boolean {
|
||||
let isShouldUpdate = true
|
||||
|
||||
if (this.total < 1) {
|
||||
console.warn(
|
||||
'indicator total setted a error num: ',
|
||||
this.total,
|
||||
' will be resetted 1'
|
||||
)
|
||||
this.total = 1
|
||||
isShouldUpdate = false
|
||||
} else if (this.total > 15) {
|
||||
console.warn(
|
||||
'indicator total setted a error num: ',
|
||||
this.total,
|
||||
' will be resetted 15'
|
||||
)
|
||||
this.total = 15
|
||||
isShouldUpdate = false
|
||||
}
|
||||
|
||||
if (this.index < 1) {
|
||||
console.warn(
|
||||
'indicator index setted a error num: ',
|
||||
this.index,
|
||||
' will be resetted 1'
|
||||
)
|
||||
this.index = 1
|
||||
isShouldUpdate = false
|
||||
} else if (this.index > this.total) {
|
||||
console.warn(
|
||||
'indicator index setted a error num: ',
|
||||
this.index,
|
||||
' will be resetted',
|
||||
this.total
|
||||
)
|
||||
this.index = this.total
|
||||
isShouldUpdate = false
|
||||
}
|
||||
|
||||
if (this.#firstRender === true) {
|
||||
this.#firstRender = false
|
||||
return true
|
||||
}
|
||||
return isShouldUpdate
|
||||
}
|
||||
|
||||
/**
|
||||
* 标准模板:
|
||||
*
|
||||
* 半径为4的球---间距8---长度为22倒角为4的圆角矩形---间距为8---半径为4的球
|
||||
*/
|
||||
render() {
|
||||
const all = []
|
||||
let hasRectFlag = false
|
||||
const width = 22 + (this.total - 1) * (4 * 2 + 8)
|
||||
const viewBox = `0 0 ${width} ${4 * 2}`
|
||||
const rectBeginX = (this.index - 1) * (4 * 2 + 8)
|
||||
|
||||
for (let i = 1; i <= this.total; i++) {
|
||||
if (i == this.index) {
|
||||
all.push(svg`<rect x=${rectBeginX} y=0 rx=4 ry=4 width=22 height=8 />`)
|
||||
hasRectFlag = true
|
||||
} else {
|
||||
let cx = 0
|
||||
if (all.length == 0) cx = 4
|
||||
else if (hasRectFlag) cx = 30 + (all.length - 1) * 16 + 4
|
||||
else cx = all.length * 16 + 4
|
||||
all.push(svg`<circle cx=${cx} cy=4 r=4 />`)
|
||||
}
|
||||
}
|
||||
|
||||
return html`
|
||||
<svg viewBox=${viewBox} xmlns="http://www.w3.org/2000/svg">${all}</svg>
|
||||
`
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
max-height: 50px;
|
||||
|
||||
--light-indicator-fill: #ffffff;
|
||||
--light-indicator-fill-rect-opacity: 0.85;
|
||||
--light-indicator-fill-circ-opacity: 0.4;
|
||||
|
||||
--dark-indicator-fill: #1b1b1b;
|
||||
--dark-indicator-fill-rect-opacity: 0.7;
|
||||
--dark-indicator-fill-circ-opacity: 0.3;
|
||||
}
|
||||
:host([column]) {
|
||||
display: inline-flex;
|
||||
width: auto;
|
||||
}
|
||||
svg {
|
||||
flex: 1;
|
||||
}
|
||||
svg rect {
|
||||
fill: var(--indicator-fill);
|
||||
fill-opacity: var(--indicator-fill-rect-opacity);
|
||||
}
|
||||
svg circle {
|
||||
fill: var(--indicator-fill);
|
||||
fill-opacity: var(--indicator-fill-circ-opacity);
|
||||
}
|
||||
:host(.light) svg rect {
|
||||
fill: var(--light-indicator-fill);
|
||||
fill-opacity: var(--light-indicator-fill-rect-opacity);
|
||||
}
|
||||
:host(.light) svg circle {
|
||||
fill: var(--light-indicator-fill);
|
||||
fill-opacity: var(--light-indicator-fill-circ-opacity);
|
||||
}
|
||||
:host(.dark) svg rect {
|
||||
fill: var(--dark-indicator-fill);
|
||||
fill-opacity: var(--dark-indicator-fill-rect-opacity);
|
||||
}
|
||||
:host(.dark) svg circle {
|
||||
fill: var(--dark-indicator-fill);
|
||||
fill-opacity: var(--dark-indicator-fill-circ-opacity);
|
||||
}
|
||||
@media (prefers-color-scheme: light) {
|
||||
:host {
|
||||
--indicator-fill: var(--light-indicator-fill);
|
||||
--indicator-fill-rect-opacity: var(--light-indicator-fill-rect-opacity);
|
||||
--indicator-fill-circ-opacity: var(--light-indicator-fill-circ-opacity);
|
||||
}
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:host {
|
||||
--indicator-fill: var(--dark-indicator-fill);
|
||||
--indicator-fill-rect-opacity: var(--dark-indicator-fill-rect-opacity);
|
||||
--indicator-fill-circ-opacity: var(--dark-indicator-fill-circ-opacity);
|
||||
}
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'indicator-page-deformation': IndicatorPageDeformation
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
import {LitElement, html, HTMLTemplateResult, CSSResultArray} from 'lit'
|
||||
import {customElement, property, queryAssignedElements} from 'lit/decorators.js'
|
||||
import '../button/button.js'
|
||||
import {OverlayStack} from '../overlay/overlay-stack'
|
||||
import {sharedStyles} from './overflowmenustyle.js'
|
||||
|
||||
@customElement('star-overflowmenu')
|
||||
|
@ -20,81 +21,36 @@ export class StarOverflowMenu extends LitElement {
|
|||
@queryAssignedElements({flatten: true})
|
||||
_evenEl: any
|
||||
|
||||
_getElement() {
|
||||
// 获取网页宽度用于判断菜单显示位置是否越界
|
||||
const bodywidth = document.documentElement.clientWidth
|
||||
const bodyheight = document.documentElement.clientHeight
|
||||
// 获取菜单所在div,用于控制menu显示或隐藏,ts默认使用Element,需转换为HTMLElement
|
||||
const mu = this.renderRoot.querySelector('#menuitem') as HTMLElement
|
||||
// 获取star-button相对屏幕的位置
|
||||
const buttonposition = this.renderRoot
|
||||
.querySelector('star-button')
|
||||
?.getBoundingClientRect()
|
||||
// star-button的top、bottom、left及right值
|
||||
const buttontop = Number(buttonposition?.top)
|
||||
const buttonbottom = Number(buttonposition?.bottom)
|
||||
const buttonleft = Number(buttonposition?.left)
|
||||
const buttonright = Number(buttonposition?.right)
|
||||
// 通过“open”判断是否显示menu
|
||||
if (this.open == true) {
|
||||
for (var i = 0; i < this._evenEl.length; i++) {
|
||||
const slotelement = this._evenEl[i]
|
||||
// 设置div显示display状态
|
||||
mu.style.display = 'block'
|
||||
// 设置显示位置类型
|
||||
// this._evenEl[i].style.position = 'fixed'
|
||||
slotelement.style.position = 'relative'
|
||||
this.open = false
|
||||
// 获取溢出菜单width及height
|
||||
const menuwidth = slotelement.getBoundingClientRect().width
|
||||
const menuheight = slotelement.getBoundingClientRect().height
|
||||
// 弹出菜单边界,rightline和bottomline分别为是否超过右侧和下侧显示区域
|
||||
const rightline = buttonright + menuwidth > bodywidth ? true : false
|
||||
const bottomline = buttonbottom + menuheight > bodyheight ? true : false
|
||||
// 右下角边界
|
||||
if (rightline && bottomline) {
|
||||
slotelement.style.left =
|
||||
-(menuwidth - (buttonright - buttonleft)) + 'px'
|
||||
slotelement.style.bottom =
|
||||
menuheight + (buttonbottom - buttontop) + 'px'
|
||||
return
|
||||
} else if (rightline) {
|
||||
// 右侧边界
|
||||
slotelement.style.right =
|
||||
menuwidth - (buttonright - buttonleft) + 'px'
|
||||
return
|
||||
} else if (bottomline) {
|
||||
// 下侧边界
|
||||
slotelement.style.bottom =
|
||||
menuheight + (buttonbottom - buttontop) + 'px'
|
||||
return
|
||||
} else {
|
||||
// 正常情况
|
||||
return
|
||||
}
|
||||
}
|
||||
public overlayStack = new OverlayStack()
|
||||
|
||||
public showmenu(e: Event) {
|
||||
if (this.overlayStack.isOpen == false) {
|
||||
// 获取响应事件节点
|
||||
const targetNode = e.target as HTMLElement
|
||||
// 获取溢出菜单
|
||||
const originalNode = this
|
||||
// 获取overlay显示内容
|
||||
const overlaycontent = this.querySelector('#menu') as HTMLElement
|
||||
// overlaycontent为空则退出
|
||||
if (!overlaycontent) return
|
||||
// 开启overlay
|
||||
this.overlayStack.openOverlay(originalNode, targetNode, overlaycontent)
|
||||
} else {
|
||||
for (var i = 0; i < this._evenEl.length; i++) {
|
||||
mu.style.display = 'none'
|
||||
this.open = true
|
||||
}
|
||||
this.overlayStack.closeOverlay()
|
||||
}
|
||||
}
|
||||
|
||||
protected firstUpdated(): void {
|
||||
this._getElement()
|
||||
}
|
||||
|
||||
getBaseMenu(): HTMLTemplateResult {
|
||||
// 为OverflowMenu绑定监听
|
||||
this.addEventListener('test', () => {
|
||||
this.overlayStack.closeOverlay()
|
||||
})
|
||||
return html`
|
||||
<star-button
|
||||
type="icononly"
|
||||
icon=${this.icon}
|
||||
@click=${this._getElement}
|
||||
id="openmenu"
|
||||
@click=${this.showmenu}
|
||||
></star-button>
|
||||
<div id="menuitem">
|
||||
<slot></slot>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
|
|
|
@ -2,22 +2,19 @@ import {css, CSSResult} from 'lit'
|
|||
|
||||
export const sharedStyles: CSSResult = css`
|
||||
:host {
|
||||
width: auto;
|
||||
width: inherit;
|
||||
max-width: 300px;
|
||||
display: block;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
#menuitem {
|
||||
width: auto;
|
||||
position: inherit;
|
||||
}
|
||||
|
||||
star-button {
|
||||
display: block;
|
||||
width: 35px;
|
||||
}
|
||||
|
||||
::slotted(star-ul) {
|
||||
display: none;
|
||||
margin: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
import {LitElement, html, TemplateResult} from 'lit'
|
||||
import {LitElement, html, TemplateResult, CSSResultArray} from 'lit'
|
||||
import {customElement, property} from 'lit/decorators.js'
|
||||
import {OverlayOpenDetail} from './overlay-types'
|
||||
import {sharedStyles} from './overlaystyle'
|
||||
|
||||
@customElement('star-activeoverlay')
|
||||
export class ActiveOverlay extends LitElement {
|
||||
public static override get styles(): CSSResultArray {
|
||||
return [sharedStyles]
|
||||
}
|
||||
|
||||
// 根节点(占位符所表示的元素的父节点)
|
||||
public root?: HTMLElement
|
||||
// 被点击的节点
|
||||
|
@ -14,9 +19,6 @@ export class ActiveOverlay extends LitElement {
|
|||
@property({reflect: true})
|
||||
public placement?: String
|
||||
|
||||
offset!: number
|
||||
skidding!: number
|
||||
|
||||
public constructor() {
|
||||
super()
|
||||
}
|
||||
|
@ -28,12 +30,9 @@ export class ActiveOverlay extends LitElement {
|
|||
// 初始化函数
|
||||
private extractDetail(detail: OverlayOpenDetail): void {
|
||||
this.placement = detail.placement
|
||||
this.offset = detail.offset
|
||||
this.skidding = detail.skidding || 0
|
||||
this.root = detail.root
|
||||
}
|
||||
|
||||
public stealOverlayContent(_: HTMLElement) {}
|
||||
// 位置更新函数
|
||||
public updatePosition = () => {
|
||||
// 设置最大显示宽度和高度
|
||||
|
@ -47,44 +46,41 @@ export class ActiveOverlay extends LitElement {
|
|||
const bodyheight = document.documentElement.clientHeight
|
||||
// 被点击的节点
|
||||
const targetnode = this.targetNode as HTMLElement
|
||||
// 获取被点击节点位置——用于越界判断和定位
|
||||
// 获取被点击节点位置——用于越界判断和定位:分别表示上下左右四个方位
|
||||
const targetNodePosition = targetnode.getBoundingClientRect()
|
||||
const targettop = Number(targetNodePosition?.top)
|
||||
const targetbottom = Number(targetNodePosition?.bottom)
|
||||
const targetleft = Number(targetNodePosition?.left)
|
||||
const targetright = Number(targetNodePosition?.right)
|
||||
// 设置样式
|
||||
this.style.position = 'relative'
|
||||
this.style.zIndex = '1'
|
||||
this.style.zIndex = '10'
|
||||
this.style.maxWidth = availableWidth + 'px'
|
||||
this.style.maxHeight = availableHeight + 'px'
|
||||
// 边界判断
|
||||
const rightline = targetright + contentwidth > bodywidth ? true : false // 右侧越界条件
|
||||
// 边界判断:targetright指被点击的节点右边界,contentwidth表示将要显示的内容的宽度
|
||||
const rightline = targetleft + contentwidth > bodywidth ? true : false // 右侧越界条件
|
||||
const bottomline =
|
||||
targetbottom + availableHeight > bodyheight ? true : false //下方越界条件
|
||||
let left: number
|
||||
let top: number
|
||||
// 右下角边界
|
||||
if (rightline && bottomline) {
|
||||
this.style.left =
|
||||
targetleft - (contentwidth - targetnode.offsetWidth) + 'px'
|
||||
this.style.top = targettop - targetnode.offsetHeight + 'px'
|
||||
return
|
||||
left = targetleft - (contentwidth - targetnode.offsetWidth)
|
||||
top = targettop - contentheight - targetnode.offsetHeight
|
||||
} else if (rightline) {
|
||||
// 右侧边界
|
||||
this.style.left =
|
||||
targetleft - (contentwidth - targetnode.offsetWidth) + 'px'
|
||||
this.style.top = targettop + targetnode.offsetHeight + 'px'
|
||||
return
|
||||
left = targetleft - (contentwidth - targetnode.offsetWidth)
|
||||
top = targettop + targetnode.offsetHeight
|
||||
} else if (bottomline) {
|
||||
// 下侧边界
|
||||
this.style.left = targetleft + 'px'
|
||||
this.style.top = targettop - contentheight + 'px'
|
||||
return
|
||||
left = targetleft
|
||||
top = targettop - contentheight - targetnode.offsetHeight
|
||||
} else {
|
||||
// 正常情况
|
||||
this.style.left = targetleft + 'px'
|
||||
this.style.top = targettop + targetnode.offsetHeight + 'px'
|
||||
return
|
||||
left = targetleft
|
||||
top = targettop + targetnode.offsetHeight
|
||||
}
|
||||
this.style.left = left + 'px'
|
||||
this.style.top = top + 'px'
|
||||
}
|
||||
|
||||
private onSlotChange(): void {
|
||||
|
|
|
@ -13,18 +13,46 @@ export class OverlayStack {
|
|||
const activeOverlay = ActiveOverlay.create(root, targetnode, content)
|
||||
// 开启状态
|
||||
this.isOpen = true
|
||||
// 为overlay添加显示内容
|
||||
activeOverlay.appendChild(content as HTMLElement)
|
||||
// 创建注释节点模板——用于替换要展示在overlay中的元素
|
||||
const placeholderTemplate: Comment = document.createComment(
|
||||
'placeholder for reparented element'
|
||||
)
|
||||
// 为overlay添加显示内容
|
||||
activeOverlay.appendChild(content as HTMLElement)
|
||||
// 占位
|
||||
activeOverlay.root?.appendChild(placeholderTemplate)
|
||||
// 将activeoverlay添加到body底部
|
||||
document.body.append(activeOverlay)
|
||||
// // 将activeoverlay添加到body底部显示div中
|
||||
let showmenu = document.querySelector('#showmenu') as HTMLElement //后续关联模态时
|
||||
if (showmenu == null) {
|
||||
showmenu = document.createElement('div')
|
||||
showmenu.setAttribute('id', 'showmenu')
|
||||
Object.assign(showmenu.style, {
|
||||
width: '100vw',
|
||||
height: '100vh',
|
||||
position: 'fixed',
|
||||
backgroundColor: '#00000033',
|
||||
justifycontent: 'center',
|
||||
})
|
||||
} else {
|
||||
showmenu.style.display = 'fixed'
|
||||
}
|
||||
// 为显示div添加overlay并将div添加到body中
|
||||
showmenu.append(activeOverlay)
|
||||
document.body.appendChild(showmenu)
|
||||
// 将activeoverlay添加到已打开overlay数组中
|
||||
this.overlays.push(activeOverlay)
|
||||
// 为showmenu显示层添加监听
|
||||
showmenu.addEventListener('click', function (e: Event) {
|
||||
console.log(e.target)
|
||||
if (e.target == this) {
|
||||
root!.dispatchEvent(
|
||||
new Event('test', {
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
})
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -41,8 +69,11 @@ export class OverlayStack {
|
|||
const content = closeactiveoverlay.restoreContent as HTMLElement
|
||||
// 替换子节点
|
||||
srcroot?.replaceChild(content, placeholder)
|
||||
const showmenu = document.querySelector('#showmenu') as HTMLElement
|
||||
// 从body中移除activeoverlay节点
|
||||
document.body.removeChild(closeactiveoverlay)
|
||||
showmenu.removeChild(closeactiveoverlay)
|
||||
// showmenu.style.display = 'none'
|
||||
document.body.removeChild(showmenu)
|
||||
this.isOpen = false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
import {css, CSSResult} from 'lit'
|
||||
export const sharedStyles: CSSResult = css`
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
div {
|
||||
max-width: 200px;
|
||||
}
|
||||
`
|
|
@ -0,0 +1,36 @@
|
|||
# 图案密码 pattern-view
|
||||
|
||||
工作职责:
|
||||
|
||||
- 由九宫圆圈组成的图案密码
|
||||
- 默认密码为 `012543` (滑动如下图)
|
||||
|
||||
```
|
||||
--- --- ---
|
||||
| * | **** | * | **** | * |
|
||||
--- --- ---
|
||||
*
|
||||
--- --- ---
|
||||
| * | **** | * | **** | * |
|
||||
--- --- ---
|
||||
|
||||
--- --- ---
|
||||
| | | | | |
|
||||
--- --- ---
|
||||
```
|
||||
|
||||
- 点击数字反馈,输入成功上滑,输入错误抖动反馈
|
||||
|
||||
### 默认
|
||||
|
||||
```
|
||||
<star-pattern-view></star-pattern-view>
|
||||
```
|
||||
|
||||
### 距离顶部的位置 `topDir` 默认`217.5px`
|
||||
|
||||
```
|
||||
<star-pattern-view topDir="300px"></star-pattern-view>
|
||||
<star-pattern-view topDir="-100px"></star-pattern-view>
|
||||
|
||||
```
|
|
@ -0,0 +1,78 @@
|
|||
import {css, CSSResult} from 'lit'
|
||||
export const sharedStyles: CSSResult = css`
|
||||
.bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-image: url(../assets/backgroud.png);
|
||||
background-repeat: no-repeat;
|
||||
background-attachment: fixed;
|
||||
background-size: cover;
|
||||
background-position: top;
|
||||
}
|
||||
|
||||
.groundGlass {
|
||||
display: none;
|
||||
position: relative;
|
||||
margin: auto;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
line-height: 100%;
|
||||
color: #fff;
|
||||
font-size: 30px;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.bg ::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -100%;
|
||||
left: -100%;
|
||||
right: -100%;
|
||||
bottom: -100%;
|
||||
background-image: url(../assets/backgroud.png);
|
||||
background-repeat: no-repeat;
|
||||
background-attachment: fixed;
|
||||
background-size: cover;
|
||||
background-position: top;
|
||||
filter: blur(10px);
|
||||
z-index: -2;
|
||||
}
|
||||
|
||||
.bg ::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(217, 217, 217, 0.65);
|
||||
z-index: -1;
|
||||
}
|
||||
/* canvas{
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 11%;
|
||||
right:0;
|
||||
bottom:0;
|
||||
margin:auto;
|
||||
} */
|
||||
/* #lockCancel {
|
||||
position: absolute;
|
||||
width: 50px;
|
||||
height: 30px;
|
||||
left: calc(50% - 50px/2);
|
||||
top: calc(81.5%);
|
||||
font-family: 'OPPOSans';
|
||||
font-weight: 900;
|
||||
font-size: 14px;
|
||||
line-height: 30px;
|
||||
color: #292929;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
} */
|
||||
`
|
|
@ -0,0 +1,191 @@
|
|||
import {html, LitElement, css, PropertyValueMap} from 'lit'
|
||||
import {customElement, property, query} from 'lit/decorators.js'
|
||||
// import {sharedStyles} from './pattern-view-style'
|
||||
|
||||
@customElement('star-pattern-view')
|
||||
export class StarPatternView extends LitElement {
|
||||
_topDir: string = ''
|
||||
_getRed: boolean = false
|
||||
static styles = css`
|
||||
:host {
|
||||
--top-dir: 217.5px;
|
||||
}
|
||||
canvas {
|
||||
margin-top: var(--top-dir);
|
||||
}
|
||||
`
|
||||
// public static override get styles(): CSSResultArray {
|
||||
// return [sharedStyles]
|
||||
// }
|
||||
@query('canvas') canvas!: HTMLCanvasElement
|
||||
@property({attribute: false}) cxt!: CanvasRenderingContext2D
|
||||
@property({type: Boolean})
|
||||
get getRed() {
|
||||
return this._getRed
|
||||
}
|
||||
set getRed(value) {
|
||||
if (this._getRed !== value) {
|
||||
this._getRed = value
|
||||
this.Draw()
|
||||
}
|
||||
}
|
||||
@property({type: Number}) top = 0
|
||||
@property({type: Number}) R = 35
|
||||
@property({type: Number}) X = 0
|
||||
@property({type: Number}) Y = 0
|
||||
@property({type: Number}) canvasWidth = document.body.offsetWidth
|
||||
@property({type: Number}) canvasHeight = document.body.offsetWidth
|
||||
@property({type: Number}) OffsetX = document.body.offsetWidth / 3.5
|
||||
@property({type: Number}) OffsetY = document.body.offsetWidth / 3.5
|
||||
@property({type: Array}) circleArr: {X: number; Y: number}[] = []
|
||||
@property({type: Array}) pwdArr: number[] = []
|
||||
@property({type: Array}) passwdArr = [0, 1, 2, 5, 4, 3]
|
||||
@property({type: String})
|
||||
get topDir() {
|
||||
return this._topDir
|
||||
}
|
||||
set topDir(value: string) {
|
||||
this.style.setProperty('--top-dir', value)
|
||||
this._topDir = value
|
||||
this.top = parseInt(this._topDir)
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<!-- <div style="height:200px;background-color:red"></div> -->
|
||||
<canvas
|
||||
id="canvas"
|
||||
@touchstart=${this.touchStart}
|
||||
@touchmove=${this.touchMove}
|
||||
@touchend=${this.touchEnd}
|
||||
></canvas>
|
||||
`
|
||||
}
|
||||
|
||||
protected firstUpdated(
|
||||
_changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>
|
||||
): void {
|
||||
this.top = this.top ? this.top : 217.5
|
||||
//canvas的高度和宽度都是
|
||||
this.canvas.width = this.canvasWidth
|
||||
this.canvas.height = this.canvasHeight
|
||||
//getContext() 方法可返回一个对象,该对象提供了用于在画布上绘图的方法和属性。
|
||||
this.cxt = this.canvas.getContext('2d')!
|
||||
this.X = (this.canvasWidth - 2 * this.OffsetX - this.R * 2 * 3) / 2
|
||||
this.Y = (this.canvasHeight - 2 * this.OffsetY - this.R * 2 * 3) / 2
|
||||
this.createCirclePoint(this.X, this.Y)
|
||||
this.Draw()
|
||||
}
|
||||
|
||||
createCirclePoint(diffX: number, diffY: number) {
|
||||
for (var row = 0; row < 3; row++) {
|
||||
for (var col = 0; col < 3; col++) {
|
||||
// 计算圆心坐标
|
||||
var Point = {
|
||||
X: this.OffsetX + col * diffX + (col * 2 + 1) * this.R,
|
||||
Y: this.OffsetY + row * diffY + (row * 2 + 1) * this.R,
|
||||
}
|
||||
this.circleArr.push(Point)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Draw(touchPoint?: {X: any; Y: any}) {
|
||||
if (this.pwdArr.length > 1) {
|
||||
this.cxt.beginPath()
|
||||
for (var i = 0; i < this.pwdArr.length; i++) {
|
||||
var pointIndex = this.pwdArr[i]
|
||||
this.cxt.lineTo(
|
||||
this.circleArr[pointIndex].X,
|
||||
this.circleArr[pointIndex].Y
|
||||
)
|
||||
}
|
||||
//锁屏线
|
||||
this.cxt.lineWidth = 2
|
||||
this.cxt.strokeStyle = this.getRed ? '#FF4040' : '#333333'
|
||||
this.cxt.stroke()
|
||||
this.cxt.closePath()
|
||||
if (touchPoint != null) {
|
||||
var lastPointIndex = this.pwdArr[this.pwdArr.length - 1]
|
||||
var lastPoint = this.circleArr[lastPointIndex]
|
||||
this.cxt.beginPath()
|
||||
this.cxt.moveTo(lastPoint.X, lastPoint.Y)
|
||||
this.cxt.lineTo(touchPoint.X, touchPoint.Y)
|
||||
this.cxt.stroke()
|
||||
this.cxt.closePath()
|
||||
}
|
||||
}
|
||||
for (var i = 0; i < this.circleArr.length; i++) {
|
||||
var Point = this.circleArr[i]
|
||||
//大圆
|
||||
this.cxt.fillStyle = 'rgb(51,51,51,0.08)'
|
||||
this.cxt.beginPath()
|
||||
this.cxt.arc(Point.X, Point.Y, this.R, 0, Math.PI * 2, true)
|
||||
this.cxt.closePath()
|
||||
this.cxt.fill()
|
||||
//小圆
|
||||
this.cxt.fillStyle = 'rgb(51,51,51,0.2)'
|
||||
this.cxt.beginPath()
|
||||
this.cxt.arc(Point.X, Point.Y, this.R - 26, 0, Math.PI * 2, true)
|
||||
this.cxt.closePath()
|
||||
this.cxt.fill()
|
||||
//滑动后小圆的颜色
|
||||
if (this.pwdArr.indexOf(i) >= 0) {
|
||||
this.cxt.fillStyle = this.getRed ? '#FF4040' : '#333333'
|
||||
this.cxt.beginPath()
|
||||
this.cxt.arc(Point.X, Point.Y, this.R - 26, 0, Math.PI * 2, true)
|
||||
this.cxt.closePath()
|
||||
this.cxt.fill()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getSelectPwd(touches: any, pwdArr: number[]) {
|
||||
for (var i = 0; i < this.circleArr.length; i++) {
|
||||
var currentPoint = this.circleArr[i]
|
||||
var xdiff = Math.abs(currentPoint.X - touches.pageX)
|
||||
/******** this.top = canvas向下移动的距离 **********/
|
||||
var ydiff = Math.abs(currentPoint.Y - touches.pageY + this.top)
|
||||
var dir = Math.pow(xdiff * xdiff + ydiff * ydiff, 0.5)
|
||||
if (dir > this.R || pwdArr.indexOf(i) >= 0) continue
|
||||
pwdArr.push(i)
|
||||
break
|
||||
}
|
||||
}
|
||||
touchStart(e: TouchEvent) {
|
||||
this.getRed = false
|
||||
this.getSelectPwd(e.touches[0], this.pwdArr)
|
||||
}
|
||||
touchMove(e: TouchEvent) {
|
||||
e.preventDefault()
|
||||
var touches = e.touches[0]
|
||||
this.getSelectPwd(touches, this.pwdArr)
|
||||
// 清除画布,0,0代表从什么位置开始,canvasWidth,canvasHeight代表清除的宽度和高度
|
||||
this.cxt = this.canvas.getContext('2d')!
|
||||
this.cxt.clearRect(0, 0, this.canvasWidth, this.canvasHeight)
|
||||
/*this.top = 手指拖动时,线条的起始位置。 */
|
||||
this.Draw({
|
||||
X: touches.pageX,
|
||||
Y: touches.pageY - this.top,
|
||||
})
|
||||
}
|
||||
touchEnd(_e: TouchEvent) {
|
||||
this.cxt.clearRect(0, 0, this.canvasWidth, this.canvasHeight)
|
||||
this.Draw()
|
||||
// if (this.pwdArr.toString() == this.passwdArr.toString()) {
|
||||
// console.log('密码正确', this.passwdArr.toString())
|
||||
// } else {
|
||||
// this.getRed = true
|
||||
// console.log('密码错误', this.pwdArr.toString())
|
||||
// this.Draw()
|
||||
// }
|
||||
this.getRed = true
|
||||
this.pwdArr = []
|
||||
this.dispatchEvent(
|
||||
new TouchEvent('end', {
|
||||
composed: true,
|
||||
})
|
||||
)
|
||||
return this.passwdArr
|
||||
}
|
||||
}
|
|
@ -2,41 +2,49 @@ import {css} from 'lit'
|
|||
|
||||
export default css`
|
||||
:host {
|
||||
width: inherit;
|
||||
display: block;
|
||||
margin: 20px auto;
|
||||
max-width: 88vw;
|
||||
}
|
||||
width: inherit;
|
||||
display: block;
|
||||
margin: 20px auto;
|
||||
max-width: 88vw;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin: 10px auto;
|
||||
max-width: 88vw;
|
||||
background: var(--bg-ul, var(--pure-white));
|
||||
box-shadow: rgb(64 60 67 / 16%) 1px 1px 5px 1px;
|
||||
border-radius: inherit; /* 外部传入 */
|
||||
}
|
||||
/* menu ul */
|
||||
:host(#iconmenu) {
|
||||
width: 320px;
|
||||
display: block;
|
||||
margin: 0;
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
header,
|
||||
footer {
|
||||
color: #888;
|
||||
margin-left: 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
ul {
|
||||
list-style: none;
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin: 10px auto;
|
||||
max-width: 88vw;
|
||||
background: var(--bg-ul, var(--pure-white));
|
||||
box-shadow: rgb(64 60 67 / 16%) 1px 1px 5px 1px;
|
||||
border-radius: inherit; /* 外部传入 */
|
||||
}
|
||||
|
||||
star-ul-base {
|
||||
margin: 8px 0 12px 0; /* override child(star-ul-base) */
|
||||
}
|
||||
header,
|
||||
footer {
|
||||
color: #888;
|
||||
margin-left: 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
footer a {
|
||||
text-decoration: unset;
|
||||
}
|
||||
star-ul-base {
|
||||
margin: 8px 0 12px 0; /* override child(star-ul-base) */
|
||||
}
|
||||
|
||||
footer a:visited {
|
||||
color: blue;
|
||||
footer a {
|
||||
text-decoration: unset;
|
||||
}
|
||||
|
||||
footer a:visited {
|
||||
color: blue;
|
||||
}
|
||||
`
|
||||
|
|
|
@ -16,10 +16,12 @@ import './components/toast/toast'
|
|||
import './components/picker/picker'
|
||||
import './components/overflowmenu/overflowmenu'
|
||||
import './components/slider/slider'
|
||||
import './components/icon-control-bar/icon-control-bar'
|
||||
import './components/icon-control-bar-group/icon-control-bar-group'
|
||||
import './components/notification/notification'
|
||||
import './components/prompt/prompt'
|
||||
import './components/digicipher/digicipher'
|
||||
// import './components/pattern-view/pattern-view'
|
||||
import './components/pattern-view/pattern-view'
|
||||
import './components/overlay/active-overlay'
|
||||
|
||||
@customElement('settings-app')
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
### 1.在普通 HTMLElement 上使用 GestureDetector 的方法:
|
||||
|
||||
```js
|
||||
import GestureDector from '@star-web-lib/gesture-detector/gesture-detector.js'
|
||||
|
||||
const myElement = document.querySelector('#myel')
|
||||
GestureDector.embedded(myElement)
|
||||
myElement.addEventListener('tap', function () {})
|
||||
|
@ -81,16 +83,16 @@ class MyElement extends StarBaseElement {
|
|||
|
||||
<center><h2>FSM状态转移表</h2></center>
|
||||
|
||||
| 当前状态 →<br>条件 ↓ | initialState | touchStartedState | touchesStartedState | holdState | panState | swipeState | pinchState | rotateState |
|
||||
| ---------------------------------- | ----------------- | ------------------- | ------------------- | ------------ | ---------- | ------------ | ------------ | ------------ |
|
||||
| 当前状态 →<br>条件 ↓ | initialState | touchStartedState | touchesStartedState | holdState | panState | swipeState | transformstate |
|
||||
| ---------------------------------- | ----------------- | ------------------- | ---------------------- | ------------ | ---------- | ------------ | -------------- |
|
||||
| 收到 touchstart | touchStartedState |
|
||||
| 收到 touchstart 且触摸点=1 | | initialState |
|
||||
| 收到 touchstart 且触摸点>1 | | touchesStartedState |
|
||||
| 收到 touchstart 且判定进行捏 | | | pinchState |
|
||||
| 收到 touchstart 且判定进行旋转 | | | rotateState |
|
||||
| 收到 touchstart 且判定进行平移 | | | panState |
|
||||
| 收到 touchstart 且判定进行捏 | | | transformstate(pinch) |
|
||||
| 收到 touchstart 且判定进行旋转 | | | transformstate(rotate) |
|
||||
| 收到 touchstart 且判定进行平移 | | | panState |
|
||||
| 收到 touchmove 且移动差值>平移阈值 | | panState |
|
||||
| 收到 touchend | | initialState | | initialState | swipeState | initialState | initialState | initialState |
|
||||
| 收到 touchend | | initialState | | initialState | swipeState | initialState | initialState |
|
||||
| hold 超时完成 | | holdState |
|
||||
|
||||
## 手势事件(Gesture Events)
|
||||
|
@ -224,3 +226,27 @@ Interface RotateEvent {
|
|||
// },
|
||||
// })
|
||||
```
|
||||
|
||||
## 注意
|
||||
|
||||
```js
|
||||
/**
|
||||
* Chrome 上调用 e.preventDedault() 可阻止右键菜单弹出,同时不会影响
|
||||
* 正常 touch 事件的分发。
|
||||
*
|
||||
* Firefox 上调用 e.preventDedault() 可阻止右键菜单弹出,但会中断 touch 事件,
|
||||
* 表现为:长按(touchstart),中断,手抬起再按(touchmove)
|
||||
*****
|
||||
* 测试长按事件时,请使用终端设备或Chrome
|
||||
*/
|
||||
this.addEventListener(
|
||||
'contextmenu',
|
||||
(e) => {
|
||||
e.stopImmediatePropagation()
|
||||
if (navigator.vendor === 'Google Inc.') {
|
||||
e.preventDefault()
|
||||
}
|
||||
},
|
||||
true
|
||||
)
|
||||
```
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
# TODO
|
||||
|
||||
1. 单击位移阈值 10px 或热区以内
|
||||
2. 双击间隔时间小于 300ms,两次点击的手指位移阈值 10px
|
||||
3. 长按的位移阈值 10px
|
||||
4. 拖放判定条件:手指进入长按状态后,手指位移阈值 10px
|
||||
5. 滑动、轻扫判定条件:手指位移阈值 20px
|
|
@ -88,6 +88,17 @@ export interface PanEvent {
|
|||
readonly fingers: number // only support 1 and 2
|
||||
readonly direction?: PanOrSwipeDirection
|
||||
readonly angle?: number
|
||||
readonly velocity?: number
|
||||
readonly absolute?: {
|
||||
// 相对touchstart触摸点的位移
|
||||
dx: number
|
||||
dy: number
|
||||
}
|
||||
readonly relative?: {
|
||||
// 相对上一次触摸点的位移
|
||||
dx: number
|
||||
dy: number
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -99,8 +110,7 @@ export interface SwipeEvent {
|
|||
readonly startTouch?: Touch
|
||||
readonly endTouch?: Touch
|
||||
readonly velocity: number
|
||||
// readonly direction: number
|
||||
readonly direction?: PanOrSwipeDirection
|
||||
readonly direction?: PanOrSwipeDirection // number
|
||||
readonly angle?: number
|
||||
}
|
||||
|
||||
|
@ -222,8 +232,6 @@ export type GestureState =
|
|||
| 'panState'
|
||||
| 'holdState'
|
||||
| 'swipeState'
|
||||
| 'rotateState'
|
||||
| 'pinchState'
|
||||
| 'transformState'
|
||||
|
||||
type PickUP<T, K extends keyof T> = T[K]
|
||||
|
@ -536,7 +544,10 @@ export default class GestureDector {
|
|||
// 就已存在第二只或更多只手指的落下, 需要转换状态以启动多指手势
|
||||
this.clearTimer('holdtimeout')
|
||||
|
||||
if (evt.touches.length > 1) {
|
||||
if (
|
||||
evt.touches.length > 1 &&
|
||||
(this.listenEvents.has('pinch') || this.listenEvents.has('rotate'))
|
||||
) {
|
||||
this.switchTo(this.touchesStartedState, evt, touch)
|
||||
} else {
|
||||
this.switchTo(this.initialState, evt, touch)
|
||||
|
@ -733,31 +744,11 @@ export default class GestureDector {
|
|||
}
|
||||
|
||||
/**
|
||||
* * Pinch:(pointers:2,threshold:0)
|
||||
* - pinchstart
|
||||
* - pinchmove
|
||||
* - pinchend
|
||||
* - pinchcancel
|
||||
* - pinch(include below all)
|
||||
* - pinchin
|
||||
* - pinchout
|
||||
*/
|
||||
private pinchState: FSMGestureState = {
|
||||
name: 'pinchState',
|
||||
init: (evt, touch) => {
|
||||
this.pinchState.touchstart?.(evt, touch)
|
||||
},
|
||||
touchstart: (_, __) => {
|
||||
this.emitEvent('pinchstart')
|
||||
},
|
||||
touchmove: null,
|
||||
touchend: null,
|
||||
touchcancel: null,
|
||||
}
|
||||
|
||||
/**
|
||||
* 针对双指手势的变换状态
|
||||
* 变换状态
|
||||
*
|
||||
* 针对双指手势,专用于处理双指捏合和旋转动作
|
||||
*
|
||||
* 囊括:rotate 和 pinch
|
||||
*
|
||||
*/
|
||||
private transformState: FSMGestureState = {
|
||||
|
@ -835,8 +826,9 @@ export default class GestureDector {
|
|||
}
|
||||
|
||||
if (this.scaled === true) {
|
||||
this.switchTo(this.pinchState, evt, touch)
|
||||
// ..
|
||||
} else if (this.rotated === true) {
|
||||
// ..
|
||||
}
|
||||
|
||||
if (this.scaled === true || this.rotated === true) {
|
||||
|
@ -1038,11 +1030,22 @@ export default class GestureDector {
|
|||
Object.freeze({
|
||||
movingTouches: evt.changedTouches,
|
||||
fingers: evt.touches.length,
|
||||
absolute: {
|
||||
dx: current.screenX - this.panstart.screenX,
|
||||
dy: current.screenY - this.panstart.screenY,
|
||||
},
|
||||
relative: {
|
||||
dx: current.screenX - this.last.screenX,
|
||||
dy: current.screenY - this.last.screenY,
|
||||
},
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
if (this.getSeriesEvent('swipe').length) {
|
||||
if (
|
||||
this.listenEvents.has('panend') ||
|
||||
this.getSeriesEvent('swipe').length
|
||||
) {
|
||||
// 测量pan的速度,提供给 swipe
|
||||
const dt = current.timeStamp - this.last.timeStamp || 10 // dt可能为0
|
||||
const vx = (current.screenX - this.last.screenX) / dt
|
||||
|
@ -1094,6 +1097,7 @@ export default class GestureDector {
|
|||
Object.freeze({
|
||||
endTouches: evt.touches,
|
||||
fingers: evt.touches.length,
|
||||
velocity: this.vc,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
@ -1124,6 +1128,7 @@ export default class GestureDector {
|
|||
// 清理资源
|
||||
this.lastPanTouchID = -1
|
||||
|
||||
// 如果检测到是第2指,提前退出
|
||||
if (isSecondFinger) {
|
||||
debugInfo('swipe::isSecondFinger', this.vc)
|
||||
if (
|
||||
|
@ -1201,15 +1206,6 @@ export default class GestureDector {
|
|||
touchcancel: null,
|
||||
}
|
||||
|
||||
// private rotateState: FSMGestureState = {
|
||||
// name: 'rotateState',
|
||||
// init: () => {},
|
||||
// touchstart: null,
|
||||
// touchmove: null,
|
||||
// touchend: null,
|
||||
// touchcancel: null,
|
||||
// }
|
||||
|
||||
// TODO: 优化调用处,添加缓存处理
|
||||
private getSeriesEvent<T extends SeriesOfEventsType>(
|
||||
type: T
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"type": "module",
|
||||
"main": "./index.js",
|
||||
"module": "./index.js",
|
||||
"main": "./gesture-detector.js",
|
||||
"module": "./gesture-detector.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"default": "./index.js"
|
||||
"default": "./gesture-detector.js"
|
||||
},
|
||||
"./gesture-detector.js": {
|
||||
"default": "./gesture-detector.js"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {html, LitElement, css} from 'lit'
|
||||
import {customElement, property} from 'lit/decorators.js'
|
||||
import {customElement} from 'lit/decorators.js'
|
||||
import '../../../components/button/button'
|
||||
import '../../../components/overlay/active-overlay'
|
||||
import {OverlayStack} from '../../../components/overlay/overlay-stack'
|
||||
|
@ -13,6 +13,9 @@ export class PanelActiveOverlay extends LitElement {
|
|||
public overlayStack = new OverlayStack()
|
||||
|
||||
public test(e: Event) {
|
||||
this.addEventListener('test', () => {
|
||||
this.overlayStack.closeOverlay()
|
||||
})
|
||||
if (this.overlayStack.isOpen == false) {
|
||||
// 获取被点击节点
|
||||
const targetNode = e.target as HTMLElement
|
||||
|
@ -40,7 +43,7 @@ export class PanelActiveOverlay extends LitElement {
|
|||
<button>这是要显示的内容</button>
|
||||
</div>
|
||||
|
||||
<div style="position: fixed; top: 98%;">
|
||||
<div style="position: fixed; top: 20%;">
|
||||
<button id="open" @click="${this.test}">点击显示overlay层</button>
|
||||
<button>这是要显示的内容</button>
|
||||
</div>
|
||||
|
@ -50,7 +53,7 @@ export class PanelActiveOverlay extends LitElement {
|
|||
<button>这是要显示的内容</button>
|
||||
</div>
|
||||
|
||||
<div style="position: fixed; top: 98%; left: 90%;">
|
||||
<div style="position: fixed; top: 80%; left: 90%;">
|
||||
<button id="open" @click="${this.test}">点击显示overlay层</button>
|
||||
<button>这是要显示的内容</button>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
import {html, LitElement} from 'lit'
|
||||
import {customElement, query} from 'lit/decorators.js'
|
||||
import {IconControlBarType} from '../../../components/icon-control-bar/icon-control-bar'
|
||||
import '../../../components/icon-control-bar/icon-control-bar'
|
||||
import {IconControlBarGroupType} from '../../../components/icon-control-bar-group/icon-control-bar-group'
|
||||
import {UlType} from '../../../components/ul/ul'
|
||||
import {HeaderBarType} from '../../../components/header-bar/header-bar'
|
||||
import '../../../components/icon-control-bar-group/icon-control-bar-group'
|
||||
import '../../../components/ul/ul'
|
||||
import '../../../components/header-bar/header-bar'
|
||||
@customElement('panel-control-center')
|
||||
export class PanelControlCenter extends LitElement {
|
||||
@query('icon-control-bar[icon="wifi-4"]') wifiIcon!: LitElement
|
||||
@query('icon-control-bar[icon="crop"]') cropBtn!: LitElement
|
||||
@query('icon-control-bar[icon="brightness"]') brightnessBtn!: LitElement
|
||||
render() {
|
||||
return html`
|
||||
<star-ul type=${UlType.ONLY_HEADER} title="icon-control-bar">
|
||||
<p>带状态的图标,点击有背景色变化,绑定settingsObserver监测值</p>
|
||||
<icon-control-bar
|
||||
type=${IconControlBarType.WITH_STATE}
|
||||
icon="wifi-4"
|
||||
bgchange="true"
|
||||
settingsKey="wifi.enabled"
|
||||
stateDesc="fgfgfhhhh"
|
||||
@click=${this}
|
||||
></icon-control-bar>
|
||||
<br />
|
||||
<p>基本图标</p>
|
||||
<icon-control-bar
|
||||
type=${IconControlBarType.BASE}
|
||||
icon="crop"
|
||||
@click=${this}
|
||||
></icon-control-bar>
|
||||
<br />
|
||||
<p>基本图标,点击有背景色变化</p>
|
||||
<icon-control-bar
|
||||
type=${IconControlBarType.BASE}
|
||||
icon="brightness"
|
||||
bgchange="true"
|
||||
settingsKey="screen.automatic-brightness"
|
||||
@click=${this}
|
||||
></icon-control-bar>
|
||||
<br />
|
||||
<p>基本图标,不带边框</p>
|
||||
<icon-control-bar
|
||||
type=${IconControlBarType.BASE_WITHOUT_BORDER}
|
||||
icon="settings"
|
||||
></icon-control-bar>
|
||||
</star-ul>
|
||||
|
||||
<star-ul type=${UlType.ONLY_HEADER} title="icon-control-bar-group">
|
||||
<p>一行带状态的图标</p>
|
||||
<icon-control-bar-group type=${IconControlBarGroupType.WITH_STATE}>
|
||||
<icon-control-bar
|
||||
type=${IconControlBarType.WITH_STATE}
|
||||
icon="wifi-4"
|
||||
bgchange="true"
|
||||
settingsKey="wifi.enabled"
|
||||
stateDesc=""
|
||||
></icon-control-bar>
|
||||
<icon-control-bar
|
||||
type=${IconControlBarType.WITH_STATE}
|
||||
icon="bluetooth"
|
||||
bgchange="true"
|
||||
settingsKey="bluetooth.enabled"
|
||||
></icon-control-bar>
|
||||
<icon-control-bar
|
||||
type=${IconControlBarType.WITH_STATE}
|
||||
icon="data"
|
||||
bgchange="true"
|
||||
settingsKey="a.enabled"
|
||||
></icon-control-bar>
|
||||
</icon-control-bar-group>
|
||||
<br />
|
||||
<br />
|
||||
<p>一个box框,框内是由短竖线分隔开的基本图标</p>
|
||||
<icon-control-bar-group type=${IconControlBarGroupType.BASE} count="4">
|
||||
<icon-control-bar
|
||||
type=${IconControlBarType.BASE_WITHOUT_BORDER}
|
||||
icon="crop"
|
||||
></icon-control-bar>
|
||||
<icon-control-bar
|
||||
type=${IconControlBarType.BASE_WITHOUT_BORDER}
|
||||
icon="video"
|
||||
></icon-control-bar>
|
||||
<icon-control-bar
|
||||
type=${IconControlBarType.BASE_WITHOUT_BORDER}
|
||||
icon="camera"
|
||||
></icon-control-bar>
|
||||
<icon-control-bar
|
||||
type=${IconControlBarType.BASE_WITHOUT_BORDER}
|
||||
icon="o"
|
||||
></icon-control-bar>
|
||||
</icon-control-bar-group>
|
||||
<br />
|
||||
</star-ul>
|
||||
|
||||
<star-ul type=${UlType.ONLY_HEADER} title="header-bar">
|
||||
<p>header带时间</p>
|
||||
<header-bar type=${HeaderBarType.ONLY_TIME}>
|
||||
<icon-control-bar
|
||||
type=${IconControlBarType.BASE_WITHOUT_BORDER}
|
||||
icon="compose"
|
||||
></icon-control-bar>
|
||||
<icon-control-bar
|
||||
type=${IconControlBarType.BASE_WITHOUT_BORDER}
|
||||
icon="settings"
|
||||
></icon-control-bar>
|
||||
</header-bar>
|
||||
<br />
|
||||
<p>header带时间/日期/星期</p>
|
||||
<header-bar type=${HeaderBarType.DATE_TIME}>
|
||||
<icon-control-bar
|
||||
type=${IconControlBarType.BASE_WITHOUT_BORDER}
|
||||
icon="settings"
|
||||
></icon-control-bar>
|
||||
</header-bar>
|
||||
<br />
|
||||
</star-ul>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'panel-control-center': PanelControlCenter
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import {html, LitElement, css} from 'lit'
|
||||
import {customElement, property} from 'lit/decorators.js'
|
||||
import {customElement} from 'lit/decorators.js'
|
||||
import '../icon/icon'
|
||||
|
||||
@customElement('panel-digicipher')
|
||||
|
@ -9,7 +9,7 @@ export class PanelDigicipher extends LitElement {
|
|||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: rgb(124, 194, 235);
|
||||
background-color: rgb(183, 211, 227);
|
||||
}
|
||||
`
|
||||
render() {
|
||||
|
|
|
@ -0,0 +1,127 @@
|
|||
import {html, css, LitElement, CSSResultGroup} from 'lit'
|
||||
import {customElement, property, query} from 'lit/decorators.js'
|
||||
import '../../../components/gauss_canvas/index'
|
||||
import GaussCanvas from '../../../components/gauss_canvas/index'
|
||||
|
||||
@customElement('panel-gauss')
|
||||
export class GaussBlur extends LitElement {
|
||||
@query('#container') container!: HTMLDivElement
|
||||
@query('gauss-canvas') canvas!: GaussCanvas
|
||||
// _src = '/src/test/panels/gauss_canvas/big.jpeg'
|
||||
@property() _src!: string
|
||||
// 'https://fanyiapp.cdn.bcebos.com/cms/image/cfacf96e5beb2a8444e016b96fb96ab6.jpg'
|
||||
@property({type: Number}) sigma: number = 2
|
||||
@property({type: Number}) topPositionSigma: number = 20
|
||||
@property({type: Number}) bottomPositionSigma: number = 2
|
||||
|
||||
_moveFlag: boolean = false
|
||||
_offsetY: number = 0
|
||||
_start: number = 0
|
||||
_moveDistance: number = 0
|
||||
set offsetY(value: number) {
|
||||
this._offsetY = -value
|
||||
this.canvas.display.style.transform = `translateY(${value}px)`
|
||||
}
|
||||
|
||||
handleEvent = (evt: TouchEvent | MouseEvent) => {
|
||||
switch (evt.type) {
|
||||
case 'touchstart':
|
||||
case 'mousedown':
|
||||
this._moveFlag = false
|
||||
if (evt instanceof MouseEvent) {
|
||||
this._start = evt.clientY
|
||||
} else {
|
||||
this._start = evt.touches[0].pageY
|
||||
}
|
||||
break
|
||||
case 'touchmove':
|
||||
case 'mousemove':
|
||||
if (this._start) {
|
||||
if (evt instanceof MouseEvent) {
|
||||
this._moveDistance = evt.clientY - this._start
|
||||
} else {
|
||||
this._moveDistance = evt.touches[0].pageY - this._start
|
||||
}
|
||||
|
||||
if (this._moveDistance > 0) this._moveDistance = 0
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
this.offsetY = this._moveDistance
|
||||
this.changeSigma()
|
||||
})
|
||||
}
|
||||
this._moveFlag = true
|
||||
|
||||
break
|
||||
case 'touchend':
|
||||
case 'mouseup':
|
||||
this._start = this._moveDistance = 0
|
||||
break
|
||||
case 'click':
|
||||
!this._moveFlag && (this.canvas.sigma ^= 10)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* change sigma fowllowing this.offsetY
|
||||
*/
|
||||
changeSigma() {
|
||||
const conHeight = this.container.offsetHeight
|
||||
const targetSigma =
|
||||
(this._offsetY / conHeight) *
|
||||
(this.topPositionSigma - this.bottomPositionSigma) +
|
||||
this.bottomPositionSigma
|
||||
this.canvas.showImmediately(targetSigma)
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
;(window as any).panel = this
|
||||
|
||||
this.addEventListener('touchstart', this)
|
||||
this.addEventListener('touchmove', this)
|
||||
this.addEventListener('touchend', this)
|
||||
this.addEventListener('mousedown', this)
|
||||
this.addEventListener('mousemove', this)
|
||||
this.addEventListener('mouseup', this)
|
||||
this.canvas.addEventListener('click', this)
|
||||
}
|
||||
handleInputFile(evt: Event) {
|
||||
const imgfile = (evt.target as HTMLInputElement).files?.[0]
|
||||
if (imgfile) {
|
||||
this._src = URL.createObjectURL(imgfile)
|
||||
}
|
||||
}
|
||||
render() {
|
||||
return html`
|
||||
<div id="container">
|
||||
<input type="file" @change=${this.handleInputFile} />
|
||||
<gauss-canvas src=${this._src} sigma=${this.sigma}></gauss-canvas>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
static styles?: CSSResultGroup | undefined = css`
|
||||
#container {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
will-change: transform;
|
||||
overflow: hidden;
|
||||
}
|
||||
input {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
gauss-canvas {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'panel-gauss': GaussBlur
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
# 指示器
|
||||
|
||||
## 页面指示器
|
||||
|
||||
### 页面圆点指示器
|
||||
|
||||
基本类型
|
||||
|
||||
### 页面横条指示器
|
||||
|
||||
圆点+横条,由 svg 组成。
|
||||
|
||||
TODO: 添加可形变动画。
|
|
@ -0,0 +1,140 @@
|
|||
import {html, css, LitElement, CSSResultArray} from 'lit'
|
||||
import {customElement, state} from 'lit/decorators.js'
|
||||
import {sharedStyles} from '../shared-styles'
|
||||
import '../../../components/indicator/indicator-page-deformation'
|
||||
|
||||
@customElement('panel-deformation-indicator')
|
||||
export class PanelDeformationIndicator extends LitElement {
|
||||
@state() total = 1
|
||||
@state() index = 1
|
||||
@state() edit = false
|
||||
|
||||
updated() {
|
||||
this.total = this.total < 1 ? 1 : this.total > 15 ? 15 : this.total
|
||||
this.index =
|
||||
this.index < 1 ? 1 : this.index > this.total ? this.total : this.index
|
||||
}
|
||||
|
||||
handleEvent(evt: Event) {
|
||||
switch (evt.type) {
|
||||
case 'click':
|
||||
evt.preventDefault() // iOS上不禁止按钮默认行为,将会双击按钮强制放缩屏幕
|
||||
switch ((evt.target as HTMLButtonElement).dataset.action) {
|
||||
case 'total++':
|
||||
this.total++
|
||||
break
|
||||
case 'total--':
|
||||
this.total--
|
||||
break
|
||||
case 'index++':
|
||||
this.index++
|
||||
break
|
||||
case 'index--':
|
||||
this.index--
|
||||
break
|
||||
case 'toggle-edit':
|
||||
this.edit = !this.edit
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<h3>基本展示</h3>
|
||||
<indicator-page-deformation
|
||||
total="1"
|
||||
index="1"
|
||||
></indicator-page-deformation>
|
||||
<indicator-page-deformation
|
||||
class="dark"
|
||||
total="1"
|
||||
index="1"
|
||||
></indicator-page-deformation>
|
||||
<indicator-page-deformation
|
||||
total="3"
|
||||
index="-1"
|
||||
edit
|
||||
></indicator-page-deformation>
|
||||
<indicator-page-deformation
|
||||
class="dark"
|
||||
total="3"
|
||||
index="-1"
|
||||
edit
|
||||
></indicator-page-deformation>
|
||||
<indicator-page-deformation
|
||||
total="5"
|
||||
index="2"
|
||||
></indicator-page-deformation>
|
||||
<indicator-page-deformation
|
||||
class="dark"
|
||||
total="5"
|
||||
index="2"
|
||||
></indicator-page-deformation>
|
||||
<indicator-page-deformation
|
||||
total="15"
|
||||
index="2"
|
||||
></indicator-page-deformation>
|
||||
<indicator-page-deformation
|
||||
class="dark"
|
||||
total="15"
|
||||
index="2"
|
||||
></indicator-page-deformation>
|
||||
<!-- todo -->
|
||||
<!-- <h3>编辑状态</h3>
|
||||
<indicator-page-deformation total="3" index="1" edit></indicator-page-deformation>
|
||||
<indicator-page-deformation total="5" index="2" edit></indicator-page-deformation>
|
||||
<indicator-page-deformation total="15" index="2" edit></indicator-page-deformation> -->
|
||||
<h3>切换状态</h3>
|
||||
<div>
|
||||
<div>
|
||||
<button data-action="total++" @click=${this}>total++</button>
|
||||
<button data-action="total--" @click=${this}>total--</button>
|
||||
<button data-action="index++" @click=${this}>index++</button>
|
||||
<button data-action="index--" @click=${this}>index--</button>
|
||||
<button data-action="toggle-edit" @click=${this}>toggle edit</button>
|
||||
</div>
|
||||
</div>
|
||||
<indicator-page-deformation
|
||||
total=${this.total}
|
||||
index=${this.index}
|
||||
?edit=${this.edit}
|
||||
></indicator-page-deformation>
|
||||
<indicator-page-deformation
|
||||
class="dark"
|
||||
total=${this.total}
|
||||
index=${this.index}
|
||||
?edit=${this.edit}
|
||||
></indicator-page-deformation>
|
||||
`
|
||||
}
|
||||
|
||||
public static override get styles(): CSSResultArray {
|
||||
return [
|
||||
sharedStyles,
|
||||
css`
|
||||
h3 {
|
||||
text-align: center;
|
||||
}
|
||||
div {
|
||||
display: flex;
|
||||
margin: auto;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
button {
|
||||
margin: auto;
|
||||
}
|
||||
indicator-page-deformation {
|
||||
height: 10px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
`,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'panel-deformation-indicator': PanelDeformationIndicator
|
||||
}
|
||||
}
|
|
@ -1,32 +1,35 @@
|
|||
import {html, css, LitElement} from 'lit'
|
||||
import {customElement, query, state} from 'lit/decorators.js'
|
||||
import {customElement, query} from 'lit/decorators.js'
|
||||
import '../../../components/grabber/home-bar-indicator'
|
||||
import {HomeBarIndicator} from '../../../components/grabber/home-bar-indicator'
|
||||
|
||||
@customElement('panel-home-indicator')
|
||||
export class PanelHomeIndicator extends LitElement {
|
||||
@query('home-indicator', true) homeIndicator!: HomeBarIndicator
|
||||
// @property({type: String}) status = this.homeIndicator?.status
|
||||
foo = ''
|
||||
|
||||
@state()
|
||||
bar = ''
|
||||
|
||||
attributeChangedCallback(name: string, _old: string, value: string): void {
|
||||
super.attributeChangedCallback(name, _old, value)
|
||||
console.log(name)
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
// setInterval(() => {
|
||||
// console.log(this.homeIndicator.movePoint)
|
||||
// }, 30)
|
||||
handleEvent(e: Event) {
|
||||
switch (e.type) {
|
||||
case 'panmove':
|
||||
console.log(e.type)
|
||||
break
|
||||
default:
|
||||
console.log(e.type)
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<home-bar-indicator></home-bar-indicator>
|
||||
<home-bar-indicator
|
||||
@swipeup=${this}
|
||||
@swipedown=${this}
|
||||
@swiperight=${this}
|
||||
@swipeleft=${this}
|
||||
@panup=${this}
|
||||
@pandown=${this}
|
||||
@panright=${this}
|
||||
@panleft=${this}
|
||||
@panmove=${this}
|
||||
></home-bar-indicator>
|
||||
`
|
||||
}
|
||||
|
||||
|
|
|
@ -3,8 +3,8 @@ import {customElement, state} from 'lit/decorators.js'
|
|||
import {sharedStyles} from '../shared-styles'
|
||||
import '../../../components/indicator/indicator-page-point'
|
||||
|
||||
@customElement('panel-indicators')
|
||||
export class PanelIndicators extends LitElement {
|
||||
@customElement('panel-point-indicator')
|
||||
export class PanelPointIndicator extends LitElement {
|
||||
@state() total = 1
|
||||
@state() index = 1
|
||||
@state() edit = false
|
||||
|
@ -123,6 +123,6 @@ export class PanelIndicators extends LitElement {
|
|||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'panel-indicators': PanelIndicators
|
||||
'panel-point-indicator': PanelPointIndicator
|
||||
}
|
||||
}
|
|
@ -0,0 +1,111 @@
|
|||
import {html, LitElement, css} from 'lit'
|
||||
import {customElement, property} from 'lit/decorators.js'
|
||||
import '../icon/icon'
|
||||
|
||||
@customElement('panel-locked')
|
||||
export class PanelLocked extends LitElement {
|
||||
@property({type: Number}) second = 30
|
||||
|
||||
static styles = css`
|
||||
.screen {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: rgb(183, 211, 227);
|
||||
}
|
||||
.lock {
|
||||
box-sizing: border-box;
|
||||
left: 50%;
|
||||
transform: translate(-50%);
|
||||
}
|
||||
|
||||
.lockup {
|
||||
position: absolute;
|
||||
width: 27px;
|
||||
height: 24px;
|
||||
top: 405px;
|
||||
border: 5px solid #6f7a89;
|
||||
border-radius: 50% 50% 0 0;
|
||||
}
|
||||
.lockdown {
|
||||
position: absolute;
|
||||
width: 39px;
|
||||
height: 25px;
|
||||
top: 424px;
|
||||
background: #6f7a89;
|
||||
border-radius: 3px 3px 6px 6px;
|
||||
}
|
||||
.cylinder-up {
|
||||
position: relative;
|
||||
top: 7.5px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background: #d9d9d9;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.cylinder-down {
|
||||
position: relative;
|
||||
top: 6.5px;
|
||||
width: 2px;
|
||||
height: 4.5px;
|
||||
background: #d9d9d9;
|
||||
border-radius: 0 0 1px 1px;
|
||||
}
|
||||
.text {
|
||||
position: absolute;
|
||||
width: 135px;
|
||||
height: 45px;
|
||||
left: calc(50% - 135px / 2);
|
||||
top: calc(50% - 45px / 2 + 11.75px);
|
||||
font-family: 'OPPOSans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 45px;
|
||||
line-height: 45px;
|
||||
text-align: center;
|
||||
color: #4d4d4d;
|
||||
}
|
||||
.try-again {
|
||||
position: absolute;
|
||||
width: 125.5px;
|
||||
height: 26.5px;
|
||||
left: calc(50% - 125.5px / 2 + 1px);
|
||||
top: calc(50% - 26.5px / 2 + 67.5px);
|
||||
font-family: 'OPPOSans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-size: 20px;
|
||||
line-height: 26.5px;
|
||||
color: #292929;
|
||||
}
|
||||
`
|
||||
render() {
|
||||
return html`
|
||||
<div class="screen">
|
||||
<div>
|
||||
<div class="lockup lock"></div>
|
||||
<div class="lockdown lock">
|
||||
<div class="cylinder-up lock"></div>
|
||||
<div class="cylinder-down lock"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text">已锁定</div>
|
||||
<div class="try-again">请${this.second}秒后再试</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
protected firstUpdated() {
|
||||
setInterval(() => {
|
||||
if (--this.second <= 0) {
|
||||
this.second = 30
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'panel-locked': PanelLocked
|
||||
}
|
||||
}
|
|
@ -1,10 +1,9 @@
|
|||
import {html, LitElement, css} from 'lit'
|
||||
import {customElement, property} from 'lit/decorators.js'
|
||||
import {html, LitElement, css, CSSResultArray} from 'lit'
|
||||
import {customElement} from 'lit/decorators.js'
|
||||
import '../../../components/button/button'
|
||||
import '../../../components/ul/ul'
|
||||
import '../../../components/li//li'
|
||||
import {UlType} from '../../../components/ul/ul'
|
||||
import {LiType} from '../../../components/li//li'
|
||||
import {baseStyles} from '../../../components/base/base-style'
|
||||
|
||||
@customElement('panel-overflowmenu')
|
||||
export class PanelOverflowMenu extends LitElement {
|
||||
|
@ -12,134 +11,214 @@ export class PanelOverflowMenu extends LitElement {
|
|||
super()
|
||||
}
|
||||
|
||||
// state用于记录展开的菜单数量,用以菜单展开状态互斥的判断
|
||||
@property({type: Number}) state = 0
|
||||
|
||||
// 关闭菜单
|
||||
closeoverflowmenu(e: any) {
|
||||
// 获取点击事件所在的标签名字
|
||||
const tagName = e.target.tagName.toLowerCase()
|
||||
// 判断是否点击的star-overflowmenu标签
|
||||
if (tagName == 'star-overflowmenu') {
|
||||
this.state++
|
||||
}
|
||||
// 如果点在空白处则关闭菜单
|
||||
if (tagName != 'star-overflowmenu') {
|
||||
// 获取所有的star-overflowmenu
|
||||
var menulist = this.shadowRoot!.querySelectorAll('star-overflowmenu')
|
||||
for (var i = 0; i < menulist.length; i++) {
|
||||
menulist[i].open = true
|
||||
var menu = menulist[i].renderRoot.querySelector(
|
||||
'#menuitem'
|
||||
) as HTMLElement
|
||||
menu.style.display = 'none'
|
||||
this.state = 0
|
||||
}
|
||||
}
|
||||
// 通过state判断是否已有展开的菜单,若已有则关闭菜单
|
||||
if (this.state > 1) {
|
||||
var menulist = this.shadowRoot!.querySelectorAll('star-overflowmenu')
|
||||
for (var i = 0; i < menulist.length; i++) {
|
||||
menulist[i].open = true
|
||||
var menu = menulist[i].renderRoot.querySelector(
|
||||
'#menuitem'
|
||||
) as HTMLElement
|
||||
menu.style.display = 'none'
|
||||
this.state = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
super.connectedCallback()
|
||||
// 添加click事件
|
||||
this.shadowRoot?.addEventListener('click', (e) => this.closeoverflowmenu(e))
|
||||
}
|
||||
render() {
|
||||
return html`
|
||||
<div>
|
||||
<star-overflowmenu icon="more">
|
||||
<star-ul type=${UlType.BASE}>
|
||||
<star-li
|
||||
type=${LiType.ONLY_EDIT}
|
||||
label="星光麒麟"
|
||||
default="星光麒麟"
|
||||
></star-li>
|
||||
<star-overflowmenu icon="more">
|
||||
<div id="menu">
|
||||
<star-ul type="base" id="iconmenu" background="var(--bg-dialog)">
|
||||
<star-button
|
||||
type="iconlabel"
|
||||
icon="info"
|
||||
label="应用详情"
|
||||
id="menuitem"
|
||||
></star-button>
|
||||
<star-button
|
||||
type="iconlabel"
|
||||
icon="delete"
|
||||
label="卸载"
|
||||
id="menuitem"
|
||||
></star-button>
|
||||
<star-button
|
||||
type="iconlabel"
|
||||
icon="download-circle"
|
||||
label="固定至Dock"
|
||||
id="menuitem"
|
||||
></star-button>
|
||||
</star-ul>
|
||||
</star-overflowmenu>
|
||||
<star-ul type="base" id="iconmenu" background="var(--bg-dialog)">
|
||||
<star-button
|
||||
type="iconlabel"
|
||||
icon="keyboard-circle"
|
||||
label="编辑屏幕"
|
||||
id="menuitem"
|
||||
></star-button>
|
||||
</star-ul>
|
||||
</div>
|
||||
</star-overflowmenu>
|
||||
|
||||
<star-overflowmenu
|
||||
icon="more"
|
||||
style="position: fixed; top: 50%; left: 50%;"
|
||||
>
|
||||
<star-ul type=${UlType.BASE}>
|
||||
<star-li
|
||||
type=${LiType.ONLY_EDIT}
|
||||
label="星光麒麟"
|
||||
default="星光麒麟"
|
||||
></star-li>
|
||||
<star-overflowmenu
|
||||
icon="more"
|
||||
style="position: fixed; top: 50%; left: 50%;"
|
||||
>
|
||||
<div id="menu">
|
||||
<star-ul type="base" id="iconmenu" background="var(--bg-dialog)">
|
||||
<star-button
|
||||
type="iconlabel"
|
||||
icon="info"
|
||||
label="应用详情"
|
||||
id="menuitem"
|
||||
></star-button>
|
||||
<star-button
|
||||
type="iconlabel"
|
||||
icon="delete"
|
||||
label="卸载"
|
||||
id="menuitem"
|
||||
></star-button>
|
||||
<star-button
|
||||
type="iconlabel"
|
||||
icon="download-circle"
|
||||
label="固定至Dock"
|
||||
id="menuitem"
|
||||
></star-button>
|
||||
</star-ul>
|
||||
</star-overflowmenu>
|
||||
<star-ul type="base" id="iconmenu" background="var(--bg-dialog)">
|
||||
<star-button
|
||||
type="iconlabel"
|
||||
icon="keyboard-circle"
|
||||
label="编辑屏幕"
|
||||
id="menuitem"
|
||||
></star-button>
|
||||
</star-ul>
|
||||
</div>
|
||||
</star-overflowmenu>
|
||||
|
||||
<star-overflowmenu
|
||||
icon="more"
|
||||
style="position: fixed; top: 0; right: 0;"
|
||||
>
|
||||
<star-ul type=${UlType.BASE}>
|
||||
<star-li
|
||||
type=${LiType.ONLY_EDIT}
|
||||
label="星光麒麟"
|
||||
default="星光麒麟"
|
||||
></star-li>
|
||||
<star-li
|
||||
type=${LiType.ONLY_EDIT}
|
||||
label="星光麒麟"
|
||||
default="星光麒麟"
|
||||
></star-li>
|
||||
<star-overflowmenu
|
||||
icon="more"
|
||||
style="position: fixed; top: 0; left: 90%;"
|
||||
>
|
||||
<div id="menu">
|
||||
<star-ul type="base" id="iconmenu" background="var(--bg-dialog)">
|
||||
<star-button
|
||||
type="iconlabel"
|
||||
icon="info"
|
||||
label="应用详情"
|
||||
id="menuitem"
|
||||
></star-button>
|
||||
<star-button
|
||||
type="iconlabel"
|
||||
icon="delete"
|
||||
label="卸载"
|
||||
id="menuitem"
|
||||
></star-button>
|
||||
<star-button
|
||||
type="iconlabel"
|
||||
icon="download-circle"
|
||||
label="固定至Dock"
|
||||
id="menuitem"
|
||||
></star-button>
|
||||
</star-ul>
|
||||
</star-overflowmenu>
|
||||
<star-ul type="base" id="iconmenu" background="var(--bg-dialog)">
|
||||
<star-button
|
||||
type="iconlabel"
|
||||
icon="keyboard-circle"
|
||||
label="编辑屏幕"
|
||||
id="menuitem"
|
||||
></star-button>
|
||||
</star-ul>
|
||||
</div>
|
||||
</star-overflowmenu>
|
||||
|
||||
<star-overflowmenu
|
||||
icon="more"
|
||||
style="position: fixed; bottom: 0; left: 0;"
|
||||
>
|
||||
<star-ul
|
||||
type=${UlType.ONLY_HEADER}
|
||||
title="头部有文字"
|
||||
text="尾部有文字"
|
||||
>
|
||||
<star-li type=${LiType.ONLY_LABEL} label="素条目"></star-li>
|
||||
<star-overflowmenu
|
||||
icon="more"
|
||||
style="position: fixed; top: 90%; left: 0;"
|
||||
>
|
||||
<div id="menu">
|
||||
<star-ul type="base" id="iconmenu" background="var(--bg-dialog)">
|
||||
<star-button
|
||||
type="iconlabel"
|
||||
icon="info"
|
||||
label="应用详情"
|
||||
id="menuitem"
|
||||
></star-button>
|
||||
<star-button
|
||||
type="iconlabel"
|
||||
icon="delete"
|
||||
label="卸载"
|
||||
id="menuitem"
|
||||
></star-button>
|
||||
<star-button
|
||||
type="iconlabel"
|
||||
icon="download-circle"
|
||||
label="固定至Dock"
|
||||
id="menuitem"
|
||||
></star-button>
|
||||
</star-ul>
|
||||
</star-overflowmenu>
|
||||
<star-ul type="base" id="iconmenu" background="var(--bg-dialog)">
|
||||
<star-button
|
||||
type="iconlabel"
|
||||
icon="keyboard-circle"
|
||||
label="编辑屏幕"
|
||||
id="menuitem"
|
||||
></star-button>
|
||||
</star-ul>
|
||||
</div>
|
||||
</star-overflowmenu>
|
||||
|
||||
<star-overflowmenu
|
||||
icon="more"
|
||||
style="position: fixed; bottom: 0; right: 0;"
|
||||
>
|
||||
<star-ul
|
||||
type=${UlType.ONLY_HEADER}
|
||||
title="头部有文字"
|
||||
text="尾部有文字"
|
||||
>
|
||||
<star-li type=${LiType.ONLY_LABEL} label="素条目"></star-li>
|
||||
<star-overflowmenu
|
||||
icon="more"
|
||||
style="position: fixed; top: 90%; left: 90%;"
|
||||
>
|
||||
<div id="menu">
|
||||
<star-ul type="base" id="iconmenu" background="var(--bg-dialog)">
|
||||
<star-button
|
||||
type="iconlabel"
|
||||
icon="info"
|
||||
label="应用详情"
|
||||
id="menuitem"
|
||||
></star-button>
|
||||
<star-button
|
||||
type="iconlabel"
|
||||
icon="delete"
|
||||
label="卸载"
|
||||
id="menuitem"
|
||||
></star-button>
|
||||
<star-button
|
||||
type="iconlabel"
|
||||
icon="download-circle"
|
||||
label="固定至Dock"
|
||||
id="menuitem"
|
||||
></star-button>
|
||||
</star-ul>
|
||||
</star-overflowmenu>
|
||||
</div>
|
||||
<star-ul type="base" id="iconmenu" background="var(--bg-dialog)">
|
||||
<star-button
|
||||
type="iconlabel"
|
||||
icon="keyboard-circle"
|
||||
label="编辑屏幕"
|
||||
id="menuitem"
|
||||
></star-button>
|
||||
</star-ul>
|
||||
</div>
|
||||
</star-overflowmenu>
|
||||
`
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
div {
|
||||
display: block;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
`
|
||||
static get styles(): CSSResultArray {
|
||||
return [
|
||||
baseStyles,
|
||||
css`
|
||||
star-ul#dialog {
|
||||
border-radius: var(--base-dialog-radius);
|
||||
}
|
||||
star-ul#menu {
|
||||
max-width: 200px;
|
||||
border-radius: var(--base-menu-radius);
|
||||
}
|
||||
star-ul#iconmenu {
|
||||
max-width: 150px;
|
||||
border-radius: var(--base-menu-radius);
|
||||
}
|
||||
star-ul#dialog {
|
||||
border-radius: var(--base-menu-radius);
|
||||
}
|
||||
star-li span.split {
|
||||
color: var(--split-line);
|
||||
}
|
||||
::slotted(div) {
|
||||
border-radius: 16px;
|
||||
}
|
||||
`,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
import {html, LitElement, css} from 'lit'
|
||||
import {customElement} from 'lit/decorators.js'
|
||||
import '../icon/icon'
|
||||
|
||||
@customElement('panel-pattern-view')
|
||||
export class PanelPatternView extends LitElement {
|
||||
static styles = css`
|
||||
.screen {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: rgb(124, 194, 235);
|
||||
}
|
||||
`
|
||||
render() {
|
||||
return html`
|
||||
<star-pattern-view></star-pattern-view>
|
||||
<!-- <star-pattern-view topDir="100px"></star-pattern-view> -->
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'panel-pattern-view': PanelPatternView
|
||||
}
|
||||
}
|
|
@ -11,9 +11,11 @@ import './icon/icon'
|
|||
import './general/general'
|
||||
import './gesture/gesture'
|
||||
import './card/card'
|
||||
import './indicators/indicators'
|
||||
import './indicators/deformation-indicator'
|
||||
import './indicators/home-indicator'
|
||||
import './indicators/point-indicator'
|
||||
import './blur/use-blur'
|
||||
import './gauss_canvas/gauss-blur'
|
||||
import './button/button'
|
||||
import './container/container'
|
||||
import './radio/radio'
|
||||
|
@ -23,10 +25,12 @@ import './overflowmenu/overflowmenu'
|
|||
import './switch/switch'
|
||||
import './slider/slider'
|
||||
import './digicipher/digicipher'
|
||||
// import './pattern-view/pattern-view'
|
||||
import './locked/locked'
|
||||
import './pattern-view/pattern-view'
|
||||
import './container/homescreen-container'
|
||||
import './toast/toast'
|
||||
import './picker/picker'
|
||||
import './control-center/control-center'
|
||||
import './notification/notification'
|
||||
|
||||
import './switch/switch'
|
||||
|
@ -131,14 +135,14 @@ export class PanelRoot extends LitElement {
|
|||
<star-li
|
||||
type=${LiType.ICON_LABEL}
|
||||
label="滑动条"
|
||||
icon="scene"
|
||||
icon="switch"
|
||||
iconcolor="#EB7347"
|
||||
href="#slider"
|
||||
></star-li>
|
||||
<hr />
|
||||
<star-li
|
||||
type=${LiType.ICON_LABEL}
|
||||
label="Digicipher"
|
||||
label="数字密码"
|
||||
icon="lock"
|
||||
iconcolor="#EB7347"
|
||||
href="#digicipher"
|
||||
|
@ -146,7 +150,15 @@ export class PanelRoot extends LitElement {
|
|||
<hr />
|
||||
<star-li
|
||||
type=${LiType.ICON_LABEL}
|
||||
label="PatternView"
|
||||
label="已锁定"
|
||||
icon="lock"
|
||||
iconcolor="#EB7347"
|
||||
href="#locked"
|
||||
></star-li>
|
||||
<hr />
|
||||
<star-li
|
||||
type=${LiType.ICON_LABEL}
|
||||
label="图案密码"
|
||||
icon="lock"
|
||||
iconcolor="#EB7347"
|
||||
href="#pattern-view"
|
||||
|
@ -192,6 +204,14 @@ export class PanelRoot extends LitElement {
|
|||
href="#overflowmenu"
|
||||
></star-li>
|
||||
<hr />
|
||||
<star-li
|
||||
type=${LiType.ICON_LABEL}
|
||||
label="control-center"
|
||||
icon="play-circle"
|
||||
iconcolor="blue"
|
||||
href="#control-center"
|
||||
></star-li>
|
||||
<hr />
|
||||
<star-li
|
||||
type=${LiType.ICON_LABEL}
|
||||
label="notification"
|
||||
|
@ -224,22 +244,6 @@ export class PanelRoot extends LitElement {
|
|||
href="#confirm"
|
||||
></star-li>
|
||||
<hr />
|
||||
<star-li
|
||||
type=${LiType.ICON_LABEL}
|
||||
label="页面圆点指示器"
|
||||
icon="accessibility"
|
||||
iconcolor="blue"
|
||||
href="#indicators"
|
||||
></star-li>
|
||||
<hr />
|
||||
<star-li
|
||||
type=${LiType.ICON_LABEL}
|
||||
label="home指示器"
|
||||
icon="accessibility"
|
||||
iconcolor="blue"
|
||||
href="#home-indicator"
|
||||
></star-li>
|
||||
<hr />
|
||||
<star-li
|
||||
type=${LiType.ICON_LABEL}
|
||||
label="毛玻璃"
|
||||
|
@ -248,6 +252,14 @@ export class PanelRoot extends LitElement {
|
|||
href="#blur"
|
||||
></star-li>
|
||||
<hr />
|
||||
<star-li
|
||||
type=${LiType.ICON_LABEL}
|
||||
label="高斯模糊"
|
||||
icon="achievement"
|
||||
iconcolor="gold"
|
||||
href="#gauss"
|
||||
></star-li>
|
||||
<hr />
|
||||
<star-li
|
||||
type=${LiType.ICON_LABEL}
|
||||
label="主屏"
|
||||
|
@ -290,7 +302,7 @@ export class PanelRoot extends LitElement {
|
|||
></star-li>
|
||||
</star-ul>
|
||||
|
||||
<star-ul type=${UlType.ONLY_HEADER} title="手势框架">
|
||||
<star-ul type=${UlType.ONLY_HEADER} title="手势">
|
||||
<star-li
|
||||
type=${LiType.ICON_LABEL}
|
||||
label="手势框架"
|
||||
|
@ -298,6 +310,32 @@ export class PanelRoot extends LitElement {
|
|||
iconcolor="red"
|
||||
href="#gesture"
|
||||
></star-li>
|
||||
<hr />
|
||||
<star-li
|
||||
type=${LiType.ICON_LABEL}
|
||||
label="home指示器"
|
||||
icon="accessibility"
|
||||
iconcolor="blue"
|
||||
href="#home-indicator"
|
||||
></star-li>
|
||||
</star-ul>
|
||||
|
||||
<star-ul type=${UlType.ONLY_HEADER} title="指示器">
|
||||
<star-li
|
||||
type=${LiType.ICON_LABEL}
|
||||
label="页面圆点指示器"
|
||||
icon="accessibility"
|
||||
iconcolor="blue"
|
||||
href="#point-indicator"
|
||||
></star-li>
|
||||
<hr />
|
||||
<star-li
|
||||
type=${LiType.ICON_LABEL}
|
||||
label="页面横条指示器"
|
||||
icon="accessibility"
|
||||
iconcolor="blue"
|
||||
href="#deformation-indicator"
|
||||
></star-li>
|
||||
</star-ul>
|
||||
|
||||
<star-ul type=${UlType.BASE}>
|
||||
|
|
|
@ -9,7 +9,7 @@ import '../icon/icon'
|
|||
import '../about/about'
|
||||
import '../icon/icon'
|
||||
import '../general/general'
|
||||
import '../indicators/indicators'
|
||||
import '../indicators/deformation-indicator'
|
||||
import '../blur/use-blur'
|
||||
|
||||
@customElement('panel-switch')
|
||||
|
|
Loading…
Reference in New Issue