From b443ad8121c9e174e4d76f168ac6f762cc3b445a Mon Sep 17 00:00:00 2001 From: wurou Date: Wed, 21 Sep 2022 19:06:43 +0800 Subject: [PATCH 01/20] =?UTF-8?q?TASK:#109654=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=B8=B8=E7=94=A8=E5=8A=9F=E8=83=BD=E5=BC=80=E5=85=B3UI?= =?UTF-8?q?=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/control-center/icon-item.ts | 318 +++++++++++++++++++++ 1 file changed, 318 insertions(+) create mode 100644 src/components/control-center/icon-item.ts diff --git a/src/components/control-center/icon-item.ts b/src/components/control-center/icon-item.ts new file mode 100644 index 0000000..e0946e4 --- /dev/null +++ b/src/components/control-center/icon-item.ts @@ -0,0 +1,318 @@ +import { html, css, LitElement, HTMLTemplateResult, nothing } from 'lit' +import { customElement, property, eventOptions, query, state } from 'lit/decorators.js' + +export enum IconType { + BASE = 'base', + BASE_WITHOUT_BORDER = 'base-without-border', + WITH_STATE = 'with-state' +} + +@customElement('icon-item') +export class IconItem extends LitElement { + @property({type: IconType}) 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; + @state({}) timer!: number; + + @query('.more-info-icon') moreInfoIcon!: HTMLDivElement; + @query('.icon-button') iconBtn!: HTMLDivElement; + + getbase(): HTMLTemplateResult | typeof nothing { + if (!this.icon) { + console.error('【icon-item】缺少 icon 参数'); + return nothing; + } + + const iconstyle = html` + + `; + + return html ` +
+ ${iconstyle} +
+ `; + } + + getbaseWithOutBorder(): HTMLTemplateResult | typeof nothing { + if (!this.icon) { + console.error('【icon-item】缺少 icon 参数'); + return nothing; + } + + return html ` +
+
+ `; + } + + getwithstate(): HTMLTemplateResult | typeof nothing { + if (!this.icon) { + console.error('【icon-item】缺少 icon 参数'); + return nothing; + } + + const iconstyle = html` + + `; + + return html` +
+

${this.stateDesc}

+
+ ${iconstyle} +
+ `; + } + + @eventOptions({passive: false}) + handleClick(event: Event) { + 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 == IconType.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 = ""; + // } + // } + + let self = this; + window.dispatchEvent(new CustomEvent("icon-item-click", { + detail: { + id: self.id ? self.id : self.icon, + isActive: isActive, + target: self + } + })); + } + + handleInfo() { + let self = this; + window.dispatchEvent(new CustomEvent("more-info-action", {detail: self.icon})); + } + + handlePress(event: Event) { + let target = event.target as HTMLElement; + if (target.nodeName == "P") { + target = target.parentElement as HTMLElement; + } + + if (this.getAttribute("disabled") === "true") { + return; + } + + let section = this.icon; + this.timer = setTimeout(() => { + let activity = new WebActivity("moz_configure_window", { + data: { + target: "device", + section: section + } + }); + activity.start(); + }, 300); + + let self = this; + window.dispatchEvent(new CustomEvent("touch-start", { + detail: { + id: self.id ? self.id : self.icon, + target: self + } + })); + } + + handlePressEnd() { + clearTimeout(this.timer); + } + + 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 IconType.BASE: + return this.getbase(); + case IconType.BASE_WITHOUT_BORDER: + return this.getbaseWithOutBorder(); + case IconType.WITH_STATE: + return this.getwithstate(); + default: + console.error('unhandled 【icon-item】 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; + } + + .with-border { + width: 100%; + height: 100%; + display: flex; + align-items: center; + border-radius: 16px; + background: var(--background-lm); + } + + .icon-button::before { + width: 48px; + height: 48px; + line-height: 48px; + text-align: center; + vertical-align: middle; + content: attr(data-icon); + font-size: 48px; + font-family: gaia-icons; + font-style: normal; + text-rendering: optimizelegibility; + font-weight: 500; + } + + .icon-with-state::before { + position: relative; + left: 22px; + } + + .icon-base { + justify-content: center; + } + + .more-info-icon { + width: 16px; + height: 16px; + position: relative; + left: 58px; + } + + .more-info-icon::after { + width: 16px; + height: 16px; + text-align: center; + vertical-align: middle; + line-height: 16px; + content: attr(data-icon); + font-size: 16px; + font-family: gaia-icons; + font-style: normal; + text-rendering: optimizelegibility; + font-weight: 500; + } + + p { + width: 100px; + height: 20px; + position: relative; + left: 34px; + color: var(--text-color-lm); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-family: OPPOSans; + font-style: normal; + font-weight: 400; + font-size: 21px; + line-height: 20px; + mix-blend-mode: normal; + } + + .active > p { + color: var(--text-color-active); + } + + :host([deep-mode]) .with-border { + width: 100%; + height: 100%; + display: flex; + align-items: center; + border-radius: 16px; + // border-radius: 8px; + background: var(--background-dm); + } + + :host([deep-mode]) p { + width: 100px; + height: 20px; + position: relative; + left: 34px; + color: var(--text-color-dm); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-family: OPPOSans; + font-style: normal; + font-weight: 400; + font-size: 21px; + line-height: 20px; + mix-blend-mode: normal; + } + ` +} + +declare global { + interface HTMLElementTagNameMap { + 'icon-item': IconItem + } +} \ No newline at end of file From 872d3a39ae63d0b09422f9a7d17f9a4da978b323 Mon Sep 17 00:00:00 2001 From: wurou Date: Wed, 21 Sep 2022 19:08:09 +0800 Subject: [PATCH 02/20] =?UTF-8?q?TASK:#109653=20#109656=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0wifi,=E8=93=9D=E7=89=99,=E7=9B=B8=E6=9C=BA,=E6=88=AA?= =?UTF-8?q?=E5=9B=BE,=E6=97=B6=E9=97=B4=E6=98=BE=E7=A4=BA=E7=AD=89UI?= =?UTF-8?q?=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/control-center/header-bar.ts | 289 ++++++++++++++++++ .../control-center/icons-one-line.ts | 137 +++++++++ 2 files changed, 426 insertions(+) create mode 100644 src/components/control-center/header-bar.ts create mode 100644 src/components/control-center/icons-one-line.ts diff --git a/src/components/control-center/header-bar.ts b/src/components/control-center/header-bar.ts new file mode 100644 index 0000000..fd9be7f --- /dev/null +++ b/src/components/control-center/header-bar.ts @@ -0,0 +1,289 @@ +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` +
+
+
+ +
+
+ `; + } + + getTimeAndDate(): HTMLTemplateResult { + return html` +
+
+
+
+
+
+ +
+
+ `; + } + + 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 { + height: 52px; + line-height: 52px; + font-family: OPPOSans; + font-style: normal; + font-weight: 400; + font-size: 52px; + color: var(--only-time-color-lm); + } + + .time-icons { + display: flex; + width: 136px; + position: relative; + right: 4px; + } + + .time-icons > ::slotted(*) { + display: flex; + } + + .time-icons > ::slotted(:last-child) { + position: relative; + left: 40px; + } + + .time-date-outer { + width: 100%; + height: 100%; + display: flex; + align-items: end; + } + + .time-date { + height: 100%; + width: 100%; + display: flex; + align-items: end; + line-height: 64px; + vertical-align: middle; + font-family: 'OPPOSans'; + font-style: normal; + font-weight: 400; + position: relative; + left: 10px; + } + + .time-date > #time { + color: var(--time-date-time-color-lm); + font-size: 64px; + } + + #date { + height: 34px; + line-height: 34px; + position: relative; + left: 22px; + font-size: 26px; + opacity: 0.6; + color: var(--time-date-date-color-lm); + mix-blend-mode: normal; + } + + .time-date-icons { + position: relative; + right: 11px; + } + + .host([deep-mode]) .time-outer > #time { + height: 52px; + line-height: 52px; + font-family: OPPOSans; + font-style: normal; + font-weight: 400; + font-size: 52px; + color: var(--only-time-color-dm); + } + + .host([deep-mode]) .time-date > #time { + color: var(--time-date-time-color-dm); + font-size: 64px; + } + + .host([deep-mode]) #date { + height: 34px; + line-height: 34px; + position: relative; + left: 22px; + font-size: 26px; + opacity: 0.6; + color: var(--time-date-date-color-dm); + mix-blend-mode: normal; + } + ` + + 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 + } +} \ No newline at end of file diff --git a/src/components/control-center/icons-one-line.ts b/src/components/control-center/icons-one-line.ts new file mode 100644 index 0000000..b6a8fa1 --- /dev/null +++ b/src/components/control-center/icons-one-line.ts @@ -0,0 +1,137 @@ +import { html, css, LitElement, HTMLTemplateResult, nothing } from 'lit' +import { customElement, property, queryAssignedElements } from 'lit/decorators.js' + +export enum IconsOneLineType { + BASE = 'base', + WITH_STATE = 'with-state' +} + +@customElement('icons-one-line') +export class IconsOneLine extends LitElement { + @property({type: IconsOneLineType}) type = ''; + @property({type: Number}) count = 2; + @queryAssignedElements({flatten: true}) slotElements!: HTMLSlotElement[]; + + getbase(): HTMLTemplateResult { + const colorstyle = this.count !== 4 + ? html` + + ` + : nothing; + return html` +
+
+ +
+ ${colorstyle} +
+ `; + } + + getIconWithState(): HTMLTemplateResult { + return html` +
+ +
+ `; + } + + firstUpdated() { + this.count = this.slotElements.length; + } + + render() { + switch (this.type) { + case IconsOneLineType.BASE: + return this.getbase(); + case IconsOneLineType.WITH_STATE: + return this.getIconWithState(); + default: + console.error('unhandled 【icons-one-line】 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); + border-radius: 16px; + } + + .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; + border-left: 2px solid var(--line-border-lm); + border-radius: 2px; + } + + .icon-only > div > ::slotted(:first-child) { + border-left-style: none; + } + + :host([deep-mode]) { + width: inherit; + height: inherit; + background: var(--background-dm); + mix-blend-mode: normal; + opacity: var(--opacity-dm); + border-radius: 16px; + } + + :host([deep-mode]) .icon-only > div > ::slotted(*) { + width: 25%; + height: 30px; + display: flex; + align-items: center; + justify-content: center; + border-left: 2px solid var(--line-border-dm); + border-radius: 2px; + } + ` +} + +declare global { + interface HTMLElementTagNameMap { + 'icons-one-line': IconsOneLine + } +} \ No newline at end of file From c692b9b7eabab0b3bf9105aabeed79e0345aa5eb Mon Sep 17 00:00:00 2001 From: wurou Date: Wed, 21 Sep 2022 19:20:41 +0800 Subject: [PATCH 03/20] =?UTF-8?q?TASK:#109655=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E4=BA=AE=E5=BA=A6=E6=9D=A1UI=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/control-center/light-slider.ts | 153 ++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 src/components/control-center/light-slider.ts diff --git a/src/components/control-center/light-slider.ts b/src/components/control-center/light-slider.ts new file mode 100644 index 0000000..84284d6 --- /dev/null +++ b/src/components/control-center/light-slider.ts @@ -0,0 +1,153 @@ +import { html, css, LitElement, HTMLTemplateResult } from 'lit' +import { customElement, property, query } from 'lit/decorators.js' +@customElement('light-slider') +export class LightSlider 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` +
+
+
+ ` + } + + 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); + border-radius: 16px; + } + + .progress { + width: var(--cover-length); + height: 100%; + position: absolute; + left: 0px; + right: 0px; + background: var(--progress-background-lm); + border-radius: 16px; + } + + .sliderBar::before { + position: absolute; + width: 48px; + height: 48px; + left: 29px; + top: 29px; + content: attr(data-icon); + font-size: 48px; + font-family: gaia-icons; + font-style: normal; + text-rendering: optimizelegibility; + font-weight: 500; + } + ` + + 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("light-slider-change", { + detail: { + value: this.value + }, + bubbles: true, + composed: true, + })); + break; + } + } + + setValue(value: number) { + console.log("light00 set value: ", value); + this.value = value; + console.log("light00 set this.value: ", this.value); + let len = Math.floor(value * this.sliderBar.offsetWidth / this.max); + this.coverLen = len.toString(); + console.log("light00 set this.coverLen: ", this.coverLen); + } +} + +declare global { + interface HTMLElementTagNameMap { + 'light-slider': LightSlider + } +} \ No newline at end of file From 6a95b0231b4201424cda320d8d98f980b05ddb30 Mon Sep 17 00:00:00 2001 From: wurou Date: Wed, 21 Sep 2022 19:22:43 +0800 Subject: [PATCH 04/20] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=B8=8B=E6=8B=89?= =?UTF-8?q?=E6=8E=A7=E4=BB=B6=E6=A0=8FUI=E7=BB=84=E4=BB=B6=E5=8F=8A?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- global.d.ts | 9 + src/components/control-center/README.md | 93 ++ .../control-center/control-center-bar.ts | 926 ++++++++++++++++++ .../control-center/gaia-container-child.js | 103 ++ .../control-center/gaia-container.js | 789 +++++++++++++++ src/components/control-center/icondb.ts | 88 ++ .../control-center/mock_settings_observer.ts | 88 ++ .../control-center/pull-down-menu.ts | 128 +++ src/index.ts | 2 + .../panels/control-center/control-center.ts | 106 ++ src/test/panels/root.ts | 17 +- tsconfig.json | 2 +- 12 files changed, 2342 insertions(+), 9 deletions(-) create mode 100644 global.d.ts create mode 100644 src/components/control-center/README.md create mode 100644 src/components/control-center/control-center-bar.ts create mode 100644 src/components/control-center/gaia-container-child.js create mode 100644 src/components/control-center/gaia-container.js create mode 100644 src/components/control-center/icondb.ts create mode 100644 src/components/control-center/mock_settings_observer.ts create mode 100644 src/components/control-center/pull-down-menu.ts create mode 100644 src/test/panels/control-center/control-center.ts diff --git a/global.d.ts b/global.d.ts new file mode 100644 index 0000000..67bf093 --- /dev/null +++ b/global.d.ts @@ -0,0 +1,9 @@ +interface WebActivity { + start(): Promise; + cancel(): void; +} + +declare var WebActivity: { + prototype: WebActivity; + new(name: string, data?: any): WebActivity; +}; \ No newline at end of file diff --git a/src/components/control-center/README.md b/src/components/control-center/README.md new file mode 100644 index 0000000..2c59bb4 --- /dev/null +++ b/src/components/control-center/README.md @@ -0,0 +1,93 @@ +# icon-item +用于显示下拉控件栏中的icon。 +分为两类: +`IconType.BASE`类型: +须传入`icon`(用于图标内容),`bgchange`(用于点击图标时的样式变化),`bgchange`未传参时,默认图标点击时不改变图标背景 +`IconType.WITH_STATE`类型: +须传入`icon`(用于图标内容),`stateDesc`(用于蓝牙、wifi状态显示) +可选参数`settingsKey`: 用于settingsObsever。 + +`IconType.BASE_WITHOUT_BORDER`类型: +须传入`icon`(用于图标内容),图标不带边框。 + + +示例: +```html + + + + + + + +``` + + +# icons-online +对`icon-item`的进一步组合、封装。 +分为两类: +`IconsOneLineType.BASE`类型: +需传入`count`,为所插入插槽元素的个数,默认数值为4; +需插入插槽元素,显示为一个box框,框内是由短竖线分隔开的基本图标。 +`IconsOneLineType.WITH_STATE`类型: +需传入`count`,为所插入插槽元素的个数,默认数值为2; +需插入插槽元素,显示为一行带状态的图标。 + +示例: +```html + + + + + + + + + + + + +``` + + +# header-bar +用于显示下拉控件栏、下拉通知栏最顶部的header。 +分为两类: +`HeaderBarType.ONLY_TIME`类型: +header中显示时间和任意图标,需插入插槽元素 +`HeaderBarType.DATE_TIME`类型: +header中显示日期、星期、时间和任意图标,需插入插槽元素 +示例: +```html + + + + + + + + + + +``` + +# control-center +下拉控件栏组件。 +包括`header-bar`和中间控件部分。中间控件部分由slot元素插入。 + +```html + +
+ +
+``` + +# control-center-bar +`control-center`实例 + +说明: 因现有下拉栏顶部的状态栏是采用`background-image:-moz-element(#statusbar-icons)`方式获取,所以使用`slot`获取。 +```html + +
+
+``` \ No newline at end of file diff --git a/src/components/control-center/control-center-bar.ts b/src/components/control-center/control-center-bar.ts new file mode 100644 index 0000000..91f5355 --- /dev/null +++ b/src/components/control-center/control-center-bar.ts @@ -0,0 +1,926 @@ +import { html, css, LitElement, HTMLTemplateResult } from 'lit' +import { customElement, property, query } from 'lit/decorators.js' +import { settingsObserver as settingsObserver} from "./mock_settings_observer"; +import { IconsOneLine, IconsOneLineType } from './icons-one-line' +import { IconItem, IconType } from './icon-item'; +import { PullDownMenuType } from './pull-down-menu'; +import './icons-one-line'; +import './icon-item'; +import './pull-down-menu'; +import './light-slider'; +import "./gaia-container"; +// import "../gaia_dialog/gaia-dialog-list.js"; +// import WifiContext from "../../js/utils/origin/wifi/wifi_context.js"; +import IconDB from "./icondb"; +import { LightSlider } from './light-slider'; +// import WifiNetworkList from "../../js/utils/origin/wifi/wifi_network_list.js"; +// import WifiItem from "../../js/utils/origin/wifi/wifi_item.js"; +// import BtTemplateFactory from '../../js/utils/origin/bluetooth/bt_template_factory.js'; +// import ListView from '../../js/utils/origin/mvvm/list_view.js'; +// import BtContext from '../../js/utils/origin/bluetooth/bluetooth_context.js'; +// import BluetoothItem from "../../js/utils/origin/bluetooth/bluetooth_item.js"; +const WIFIENABLED = "wifi.enabled"; +// const BLUETOOTHENABLED = "bluetooth.enabled"; +// const BRIGHTNESS = "screen.brightness"; +// const AUTOBRIGHTNESS = "screen.automatic-brightness"; +const GEOLOCATIONENABLED = "geolocation.enabled"; +const WIFI_HOTSPOT = "tethering.wifi.enabled"; +const LOCK_SCREEN_KEY = "screen.orientation.lock"; +// const AUDIO_VOLUME_NOTIFICATION = "audio.volume.notification"; +// const VIBRATEENABLED = "vibration.enabled"; +// const WIFI_SSID_KEY = "tethering.wifi.ssid"; +// const WIFI_SECURITY_KEY = "tethering.wifi.security.type"; +// const WIFI_PASSWORD_KEY = "tethering.wifi.security.password"; + +let _debug = false; +_debug = true; +let debug = function (msg?: string | any) { + console.log(msg); + }; +if (_debug) { + debug = function btp_debug(msg) { + console.log('--> [ControlCenter]: ' + msg); + }; +} + +@customElement('control-center-bar') +export class ControlCenterBar extends LitElement { + @query('gaia-container') icons!: any; + @query('gaia-dialog-list#wifi-list') wifiListDialog!: any; + @query('gaia-dialog-list#bluetooth-list') bluetoothListDialog!: any; + @query('icons-one-line[type="base"]') cameraPhoto!: IconsOneLine; + @query('icon-item[icon="bluetooth"]') bluetooth!: IconItem; + @query('icon-item[icon="wifi-4"]') wifi!: IconItem; + @query('icon-item[icon="brightness"]') autoBrightness!: IconItem; + @query('#screen-brightness') brightness!: LightSlider; + @property({type: IconDB}) icondb = new IconDB(); + @property() settings = { + wifi: false, + airplaneMode: false, + data: false, + bluetooth: false, + wifihotspot: false, + geolocation: false, + screenLock: false, + brightness: 1, + autoBrightness: false, + }; + @property({type: Number}) iconsLeft = 0; + @property({type: Number}) iconsRight = 0; + @property({type: Boolean}) airplaneModeSwitching = false; + @property({type: Number}) lastWindowWidth = window.innerWidth; + @property({type: Number}) lastWindowHeight = window.innerHeight; + @property() pairedDevicesListView = null; + @property({type: String}) iconOnlyElements = ["data", "airplane-mode", "geolocation", "wifi-hotspot", "dark-mode", "sound", "auto-rotate", "addons"]; + @property({type: []}) startupOrder !: any[]; + @property({type: Boolean}) draggable = false; + @property({type: Number}) minRange = 10; // 最小滑动距离 + @property() touchAction = { // 触摸开始落点 + start: { + pageX: 0, + pageY: 0, + clientX: 0, + clientY: 0 + }, + + last: { // 触摸结束落点 + pageX: 0, + pageY: 0, + clientX: 0, + clientY: 0 + } + } + @property({type: Number}) preVolumeValue = 1; + @property({type: IconItem}) wifiHotspot!: IconItem; + @property({type: IconItem}) autoRotate!: IconItem; + @property({type: IconItem}) sound!: IconItem; + @property({type: IconItem}) airplaneMode!: IconItem; + @property({type: IconItem}) data!: IconItem; + @property({type: IconItem}) geolocation!: IconItem; + @property({type: Number}) timer!: number; + render(): HTMLTemplateResult { + console.log("dddd render"); + return html` + + + + + + +
+ +
+
+ + +
+ + + + + + +
+
+
+
+ + +
+ ` + } + + static styles = css` + :host { + --background-color-lm: rgba(217, 217, 217, 0.65); + --background-color-dm: rgba(89, 89, 89, 0.68); + width: inherit; + height: inherit; + display: block; + background: var(--background-color-lm); + backdrop-filter: blur(120px); + transform: translateY(0); + transition: transform 300ms ease, visibility 300ms; + visibility: hidden; + } + + :host([open]) { + visibility: visible; + } + + ::slotted(div) { + background-image: -moz-element(#statusbar-icons); + background-repeat: no-repeat; + width: 100%; + height: 3rem; + filter: none; + // background-position: 600px center; + background-position: right; + } + + pull-down-menu { + // width: 100%; + // height: calc(100% - 3rem) + position; absolute; + top: 140px; + left: 628px; + width: 512px; + } + + icons-one-line { + width: 512px; + height: 112px; + } + + icons-one-line[type='with-state'] { + position: relative; + top: 40px; + } + + .icon-section { + // width: inherit; + // height: 238px; + + width: 540px; + height: 268px; + position: relative; + top: 72px; + + left: -9px; + } + + gaia-container { + width: 100%; + height: 100%; + display: flex; + flex-flow: row wrap; + align-content: flex-start; + box-sizing: padding-box; + } + + .icon-container { + // height: 119px; + // width: 128px; + height: 134px; + width: 135px; + position: relative; + display: inline-block; + } + + .icon-container > icon-item { + position: absolute; + top: 0; + left: 0; + transition: transform 0.2s, opacity 0.2s; + transform: translate(12px, 7.5px); + } + + gaia-container:not(.loading) > .gaia-container-child:not(.added):not(.dragging) { + transition: transform 0.2s; + } + + gaia-container > .gaia-container-child.dragging { + z-index: 1; + will-change: transform; + } + + .brightness { + display: flex; + align-items: center; + justify-content: space-between; + + position: relative; + // top: 104px; + top: 89px; //104-15 + } + + #screen-brightness { + width: 378px; + height: 104px; + } + + icons-one-line[type='base'] { + position: relative; + top: 136px; + } + + #toggle-button { + width: 88px; + height: 10px; + + background: rgba(255, 255, 255, 0.35); + border-radius: 16px; + } + + .media-player { // 占位隐藏 + // width: inherit; + // height: 134px; + // position: relative; + // top: 200px; + // border: 1px solid white; + + // height: 67px; + // top: 100px; + // border: 0.5px solid white; + } + + .button-wrapper { + position: relative; + top: 168px; + display: flex; + justify-content: center; + } + + :host([dark-mode]) { + width: inherit; + height: inherit; + display: block; + background: var(--background-color-dm); + backdrop-filter: blur(120px); + } + + [disabled="true"] { + pointer-events: none; + opacity: 0.2; + } + ` + + firstUpdated() { + this.iconAdded().then(() => { + this.monitorWifiChange(); + this.monitorTetheringStatusChange(); + window.addEventListener("send-to-control-center", this.toggleActive.bind(this)); + this.monitorAirplaneModeChange(); + this.monitorBluetoothChange(); + + this.cameraPhoto.addEventListener("click", this.handleCamera.bind(this)); + // ["click", "touchstart", "touchend", "drag-end", + ["touch-start", "touchend", "drag-end", + "drag-rearrange", "drag-finish"].forEach(eventName => { + this.icons.addEventListener(eventName, this.handleContainerEvent.bind(this)) + }); + + + [this.wifiListDialog, this.bluetoothListDialog].forEach(dialog => { + dialog.addEventListener("more-settings", this.handleNavigate.bind(this)); + }); + + window.addEventListener("touchstart", this.handleStart.bind(this)); + window.addEventListener("touchmove", this.handleMove.bind(this)); + window.addEventListener("editor-action", this.handleEditor.bind(this)); + window.addEventListener("icon-item-click", this.handleIconButtonClick.bind(this)); + window.addEventListener("more-info-action", this.showListDialog.bind(this)); + }); + + } + + iconAdded() { + return new Promise((resolve: (value: void) => void) => { + this.icondb.init().then(() => { + // return this.icondb.getAll((result: any) => { + // console.log("iconAdded icondb getAll: ", result); + + // if (result) { + // let id = result.id; + // this.startupOrder.push(id); + // this.iconOnlyElements = this.startupOrder; + // } + // return Promise.resolve(); + // }); + }).then(() => { + this.addIcon(); + resolve(); + }); + }); + } + + toggleActive() { + if (this.autoBrightness.active == this.settings.autoBrightness) { + this.autoBrightness.activeOrInactive(!this.settings.autoBrightness); + } + if (this.geolocation.active == this.settings.geolocation) { + this.geolocation.activeOrInactive(!this.settings.geolocation); + } + + if (this.wifiHotspot.active == this.settings.wifihotspot) { + this.wifiHotspot.activeOrInactive(!this.settings.wifihotspot); + } + + if (this.autoRotate.active == this.settings.screenLock) { + + this.autoRotate.activeOrInactive(!this.settings.screenLock); + } + + if (this.brightness.value == this.settings.brightness) { + + // this.brightness.value = this.settings.brightness; + this.brightness.setValue(this.settings.brightness); + } + } + + + monitorWifiChange() { + // WifiItem(this.wifiStatusText).enabled = true; + // WifiItem(this.wifi.stateDesc).enabled = true; + window.addEventListener("wifi-enabled", this.handleWifiChange.bind(this)); + window.addEventListener("wifi-disabled", this.handleWifiChange.bind(this)); + window.addEventListener("wifi-statuschange", this.handleWifiChange.bind(this)); + } + + monitorAirplaneModeChange() { + + window.addEventListener("send-airplaneMode-status", (evt: any) => { + let value = evt.detail.value; + this.airplaneModeSwitching = false; + let target = this.airplaneMode; // 飞行模式element + + switch (value) { + case "enabled": + this.setSettingEnabled("airplaneMode", true); + target.activeOrInactive(false); // addClass + this.wifiHotspot.setAttribute("disabled", "true"); // 飞行模式下禁用wifi热点和移动数据 + this.data.setAttribute("disabled", "true"); + if (this.data.active) { + this.data.activeOrInactive(true); + } + break; + case "disabled": + this.setSettingEnabled("airplaneMode", false); + target.activeOrInactive(true);// removeClass + this.wifiHotspot.removeAttribute("disabled"); + this.data.removeAttribute("disabled"); + if (!this.data.active) { + this.data.activeOrInactive(false); + } + break; + case "enabling": + this.airplaneModeSwitching = true; + break; + case "disabling": + this.airplaneModeSwitching = true; + break; + } + }); + } + + monitorBluetoothChange() { + window.addEventListener("bluetooth-enabled", this.handleBlueToothChange.bind(this)); + window.addEventListener("bluetooth-disabled", this.handleBlueToothChange.bind(this)); + } + + monitorTetheringStatusChange() { + window.addEventListener("tethering-status-change", evt => { + settingsObserver.getValue(WIFIENABLED).then(wifiEnabled => { + if (!wifiEnabled && (evt as any).detail === 0) { // wifi打开状态下,wifi热点打开再关闭, 应恢复wifi + settingsObserver.setValue([{name: WIFIENABLED, value: true }]); + } + }); + }); + } + + setSettingEnabled(settingKey: string, enabled: any) { + (this.settings as any)[settingKey] = enabled; + } + + onPairedDeviceItemClick(deviceItem: any) { + debug('onPairedDeviceItemClick(): deviceItem.address = ' + + deviceItem.address); + debug('onPairedDeviceItemClick(): deviceItem.paired = ' + + deviceItem.paired); + } + + handleWifiChange(evt: Event) { + evt.preventDefault(); + switch (evt.type) { + case "wifi-enabled": + // debug("handleWifiChange wifi-enabled!!"); + this.setSettingEnabled("wifi", true); + this.wifi.activeOrInactive(false); + this.scan(); + break; + case "wifi-disabled": + // debug("handleWifiChange wifi-disabled!!"); + this.setSettingEnabled("wifi", false); + this.wifi.activeOrInactive(true); + /* + WifiItem(this.wifi.stateDesc).enabled = false; + */ + break; + case "wifi-statuschange": + // debug("handleWifiChange wifi-statuschange!!"); + let scanStates = new Set(["connected", "connectingfailed", "disconnected"]); + if (scanStates.has((evt as any).detail.event.status)) { + this.scan(); + } + break; + } + } + + handleBlueToothChange(event: Event) { + // event.preventDefault(); + switch (event.type) { + case "bluetooth-enabled": + this.setSettingEnabled("bluetooth", true); + this.bluetooth.activeOrInactive(false); + + // let pairedDeviceTemplate = + // BtTemplateFactory("paired", this.onPairedDeviceItemClick.bind(this)); + + // this.pairedDevicesListView = ListView(this.bluetoothListDialog, + // BtContext.getPairedDevices(), + // pairedDeviceTemplate); + (this.pairedDevicesListView as any).enabled = true; + break; + case "bluetooth-disabled": + this.setSettingEnabled("bluetooth", false); + this.bluetooth.activeOrInactive(true); + if (this.pairedDevicesListView) { + (this.pairedDevicesListView as any).enabled = false; + } + break; + } + } + + handleEditor() { + let dragAndDrop = this.icons.getAttribute("drag-and-drop"); + this.icons.setAttribute("drag-and-drop", (!dragAndDrop).toString()); + this.draggable = !dragAndDrop; + } + + handleSettings() { + let activity = new WebActivity("configure", {}); + activity.start().then(() => {}, () => {}); + } + + showListDialog(event: Event) { + let target = (event as any).detail; + switch (target) { + case "wifi-4": + this.wifiListDialog.open(event); + break; + case "bluetooth": + this.bluetoothListDialog.open(event); + break; + } + } + + handleIconButtonClick(event: any) { + let target = event.detail.target; + console.log("handleIconButtonClick target: ", target); + console.log("handleIconButtonClick event.detail: ", event.detail); + let isContainer = target.parentElement?.parentElement?.parentElement?.id == "icon-only"; + if (isContainer && this.draggable) { + return; + } + + let isActive = event.detail.isActive; + let id = event.detail.id; + + switch (id) { + case "wifi": + if (this.airplaneModeSwitching) { + return; + } + this.fireEvent(id, !isActive); + break; + case "bluetooth": + if (this.airplaneModeSwitching) { + return; + } + let enabled = this.settings.bluetooth; + var toggleb = enabled ? + "request-disable-bluetooth" : + "request-enable-bluetooth"; + this.dispatchEvent( + new CustomEvent(toggleb, { + bubbles: true, + composed: true, + }) + ); + break; + case "screen-auto-brightness": + this.fireEvent(id, !isActive); + break; + case "airplane-mode": + if (this.airplaneModeSwitching) { + return; + } + + var toggle = isActive ? + "request-airplane-mode-disable" : + "request-airplane-mode-enable"; + this.dispatchEvent( + new CustomEvent(toggle, { + bubbles: true, + composed: true, + }) + ); + if (isActive) { + this.wifiHotspot.removeAttribute("disabled"); + this.data.removeAttribute("disabled"); + } else { + this.wifiHotspot.setAttribute("disabled", "true"); + this.data.setAttribute("disabled", "true"); + } + break; + case "geolocation": + this.fireEvent(id, !isActive); + break; + case "wifi-hotspot": + if (this.airplaneModeSwitching) { + return; + } + this.fireEvent(id, !isActive); + break; + case "dark-mode": + break; + + case "sound": + if (this.sound.icon == "alarm") { + this.sound.icon = "mute"; + this.fireEvent("mute", 0); + } else if (this.sound.icon == "mute") { + this.sound.icon = "alarm"; + this.fireEvent("alarm", this.preVolumeValue); + } + break; + + case "auto-rotate": + this.fireEvent(id, !isActive); + break; + } + } + + handleIconButtonPress(event: any) { + let target = event.detail.target; + let isContainer = target.parentElement?.parentElement?.parentElement?.id == "icon-only"; + + if (isContainer && this.draggable) { + return; + } + + if (target.className == "icon-container") { + target = target.children[0]; + } + + if (target.getAttribute("disabled") === "true") { + return; + } + + this.close(); + let section = target.id; + this.timer = setTimeout(() => { + let activity = new WebActivity("moz_configure_window", { + data: { + target: "device", + section: section + } + }); + activity.start(); + }, 800); + } + + handleIconButtonPressEnd(event: any) { + let target = event.target; + let isContainer = target.parentElement?.parentElement?.parentElement?.id == "icon-only"; + if (isContainer && this.draggable) { + return; + } + clearTimeout(this.timer); + } + + handleNavigate(event: any) { + let section = event.target.id; + let activity = new WebActivity("moz_configure_window", { + data: { + target: "device", + section: section + } + }); + activity.start(); + } + + handleCamera(event: any) { + let target = event.target.icon; + switch (target) { + case "crop": + this.dispatchEvent( + new CustomEvent("volumedown+sleep", { + bubbles: true, + composed: true, + }) + ); + this.close(); + break; + case "video": + let takeVideo = new WebActivity("record", { + type: "videos" + }); + takeVideo.start(); + this.close(); + break; + case "camera": + let takePhotos = new WebActivity("record", { + type: "photos" + }); + takePhotos.start(); + this.close(); + break; + case "shut-down": + // powerManager.powerOff(); + break; + } + } + + handleContainerEvent(event: Event) { + switch (event.type) { + case "icon-item-click": + this.handleIconButtonClick(event); + break; + case "touch-start": + this.handleIconButtonPress(event); + break; + case "touchend": + this.handleIconButtonPressEnd(event); + break; + case "drag-end": + case "drag-rearrange": + case "drag-finish": + this.handleContainerDrag(event); + break; + } + } + + handleContainerDrag(e: any) { + switch(e.type) { + // Activate drag-and-drop immediately for selected icons + case "touchstart": + this.icons.dragAndDropTimeout = 0; + break; + + case "drag-finish": + debug("Drag-finish"); + // Restore normal drag-and-drop after dragging selected icons + this.icons.dragAndDropTimeout = -1; + break; + + // Handle icon editing and dragging to the end of the icon grid. + case "drag-end": + debug("Drag-end, target: " + (e.detail.dropTarget ? + e.detail.dropTarget.firstElementChild : "none"));//e.detail.target --> div.icon-container + + if (e.detail.dropTarget === null && + e.detail.clientX >= this.iconsLeft && + e.detail.clientX < this.iconsRight) { + // If the drop target is null, and the client coordinates are + // within the panel, we must be dropping over the start or end of + // the container. + e.preventDefault(); + let bottom = e.detail.clientY < this.lastWindowHeight / 2; + debug("Reordering dragged icon to " + + (bottom ? "bottom" : "top")); + this.icons.reorderChild(e.detail.target, + bottom ? this.icons.firstChild : null as any, + this.storeIconOrder.bind(this)); + break; + } + break; + + // Save the icon grid after rearrangement + case "drag-rearrange": + debug("Drag rearrange"); + this.storeIconOrder(); + break; + } + } + + scan() { + // WifiNetworkList(this.wifiListDialog).scan(); + } + + clearList(enabled: boolean) { + console.log(enabled); + + // WifiNetworkList(this.wifiListDialog).clear(enabled); + } + + activeOrInactive(isActive: boolean, element: HTMLElement) { + if (element.id === "sound") { + element.classList.add("active"); + return; + } + isActive ? element.classList.remove("active") : + element.classList.add("active"); + } + + addIcon() { + this.iconOnlyElements.forEach((icon) => { + let iconDiv = document.createElement("icon-item"); + iconDiv.type = "base"; + iconDiv.id = icon; + let key = null; + switch (icon) { + case "data": + iconDiv.icon = "data"; + iconDiv.bgchange = true; + break; + case "airplane-mode": + iconDiv.icon = "airplane"; + iconDiv.bgchange = true; + break; + case "geolocation": + key = GEOLOCATIONENABLED; + iconDiv.icon = "location"; + iconDiv.bgchange = true; + break; + case "wifi-hotspot": + key = WIFI_HOTSPOT; + iconDiv.icon = "tethering"; + iconDiv.bgchange = true; + break; + case "dark-mode": + iconDiv.icon = "themes"; + iconDiv.bgchange = true; + break; + case "auto-rotate": + key = LOCK_SCREEN_KEY; + iconDiv.icon = "sync"; + iconDiv.bgchange = true; + break; + case "sound": + iconDiv.icon = "alarm"; + iconDiv.id = "sound"; + iconDiv.bgchange = true; + break; + // 填充 无意义 + case "addons": + iconDiv.icon = "addons"; + iconDiv.id = "addons"; + iconDiv.bgchange = true; + break; + } + + if (key) { + iconDiv.settingsKey = key; + } + + // iconDiv.id = icon; + // iconDiv.setAttribute("id", icon); + (this as any)[this.toCamelCase(iconDiv.id)] = iconDiv; + this.addIconContainer(iconDiv); + }); + } + + addIconContainer(iconDiv: IconItem) { + let container = document.createElement("div") as HTMLElement; + container.classList.add("icon-container"); + (container as any).order = -1; + container.appendChild(iconDiv); + + if (!container.parentNode) { + this.icons.appendChild(container); + this.refreshGridSize(); + } + } + + refreshGridSize() { + let children = this.icons.children; + let visibleChildren = 0; + let cols = 4; + let firstVisibleChild = -1; + for (let i = 0, iLen = children.length; i < iLen; i++) { + // if ((children[i] as HTMLElement).style.display !== "none") { + visibleChildren ++; + if (firstVisibleChild === -1) { + firstVisibleChild = i; + this.iconsLeft = this.icons.getChildOffsetRect((children[i]) as HTMLElement).left; + } else if (visibleChildren === cols) { + this.iconsRight = this.icons.getChildOffsetRect(children[i] as HTMLElement).right; + } + // } + } + } + + toCamelCase(str: string) { + return str.replace(/\-(.)/g, function replacer(str, p1) { + console.log(str); + return p1.toUpperCase(); + }); + } + + storeIconOrder() { + let storedOrders = []; + let children = this.icons.children; + for (let i = 0, iLen = children.length; i < iLen; i++) { + let icon = children[i].firstElementChild as Element; + storedOrders.push({ id: icon.id, order: i }); + } + this.icondb.set(storedOrders).then( + () => {}, + (e) => { + console.error("Error storing icon order", e); + }); + } + + handleStart(event: TouchEvent) { + this.touchAction.start.pageX = event.touches[0].pageX; + this.touchAction.start.pageY = event.touches[0].pageY; + } + + handleMove(event: TouchEvent) { + this.touchAction.last.pageX = event.touches[0].pageX; + this.touchAction.last.pageY = event.touches[0].pageY; + let distanceY = this.touchAction.last.pageY - this.touchAction.start.pageY; + + if (this.touchAction.start.pageY > 0 && + this.touchAction.start.pageY < this.minRange && + this.touchAction.start.pageX > window.screen.width / 2 + && distanceY >= this.minRange) { + this.open(); + } + + if (this.touchAction.start.pageY > window.screen.height - this.minRange + && this.touchAction.start.pageY <= window.screen.height) { + this.close(); + } + } + + open() { + let isOpen = this.getAttribute("open"); + if (isOpen !== "") { + this.setAttribute("open", ""); + } + } + + close() { + let isOpen = this.getAttribute("open"); + if (isOpen == "") { + this.removeAttribute("open"); + } + } + + fireEvent(name: string, value: string | boolean | number) { + this.dispatchEvent( + new CustomEvent("control-center-dispatched", { + detail: { + name, + value + }, + bubbles: true, + composed: true, + }) + ) + } +} + +declare global { + interface HTMLElementTagNameMap { + 'control-center-bar': ControlCenterBar + } +} \ No newline at end of file diff --git a/src/components/control-center/gaia-container-child.js b/src/components/control-center/gaia-container-child.js new file mode 100644 index 0000000..2e94f9e --- /dev/null +++ b/src/components/control-center/gaia-container-child.js @@ -0,0 +1,103 @@ + +export default class GaiaContainerChild { + constructor(element) { + this._element = element; + this.markDirty(); + } + + get element() { + return this._element; + } + + /** + * The element that will contain the child element and control its position. + */ + get container() { + if (!this._container) { + // Create container + let container = document.createElement("div"); + container.classList.add("gaia-container-child"); + container.style.position = "absolute"; + container.style.top = "0"; + container.style.left = "0"; + container.appendChild(this.element);//this.element是div.icon-container + + this._container = container; + } + return this._container; + } + + /** + * The element that will be added to the container that will + * control the element's transform. + */ + get master() { + if (!this._master) { + // Create master + let master = document.createElement("div"); + master.style.visibility = "hidden"; + this._master = master; + + } + return this._master; + } + + /** + * Clears any cached style properties. To be used if elements are + * manipulated outside of the methods of this object. + */ + markDirty() { + this._lastElementWidth = null; + this._lastElementHeight = null; + this._lastElementDisplay = null; + this._lastElementOrder = null; + this._lastMasterTop = null; + this._lastMasterLeft = null; + } + + /** + * Synchronise the size of the master with the managed child element. + */ + synchroniseMaster() { + let master = this.master; + let element = this.element; + + let style = window.getComputedStyle(element); + let display = style.display; + let order = style.order; + let width = element.offsetWidth; + let height = element.offsetHeight; + + if (this._lastElementWidth !== width || + this._lastElementHeight !== height || + this._lastElementDisplay !== display || + this._lastElementOrder !== order) { + this._lastElementWidth = width; + this._lastElementHeight = height; + this._lastElementDisplay = display; + this._lastElementOrder = order; + + master.style.width = width + "px"; + master.style.height = height + "px"; + master.style.display = display; + master.style.order = order; + } + } + + /** + * Synchronise the container's transform with the position of the master. + */ + synchroniseContainer() { + let master = this.master; + let container = this.container; + let top = master.offsetTop; + let left = master.offsetLeft; + + if (this._lastMasterTop !== top || + this._lastMasterLeft !== left) { + this._lastMasterTop = top; + this._lastMasterLeft = left; + container.style.transform = "translate(" + left + "px, " + top + "px)"; + } + } +} \ No newline at end of file diff --git a/src/components/control-center/gaia-container.js b/src/components/control-center/gaia-container.js new file mode 100644 index 0000000..c1f1623 --- /dev/null +++ b/src/components/control-center/gaia-container.js @@ -0,0 +1,789 @@ +import GaiaContainerChild from "./gaia-container-child.js"; + +/** + * The time, in ms, to wait for an animation to start in response to a state + * change, before removing the associated style class. + */ +const STATE_CHANGE_TIMEOUT = 100; + +/** + * The time, in ms, to wait before initiating a drag-and-drop from a + * long-press. + */ +const DEFAULT_DND_TIMEOUT = 300; + +/** + * The distance, in CSS pixels, in which a touch or mouse point can deviate + * before it being discarded for initiation of a drag-and-drop action. + */ +const DND_THRESHOLD = 5; + +/** + * The minimum time between sending move events during drag-and-drop. + */ +const DND_MOVE_THROTTLE = 50; + +class GaiaContainer extends HTMLElement { + constructor() { + super(); + this.attachShadow({ mode: "open" }); + this.shadowRoot.innerHTML = this.template; + } + + connectedCallback() { + this._frozen = false; + this._pendingStateChanges = []; + this._children = []; + this._dnd = { + // Whether drag-and-drop is enabled + enabled: false, + + // The time, in ms, to wait before initiating a drag-and-drop from a + // long-press + delay: DEFAULT_DND_TIMEOUT, + + // Timeout used to initiate drag-and-drop actions + timeout: null, + + // The child that was tapped/clicked + child: null, + + // Whether a drag is active + active: false, + + // The start point of the drag action + start: { + pageX: 0, + pageY: 0, + clientX: 0, + clientY: 0 + }, + + // The last point of the drag action + last: { + pageX: 0, + pageY: 0, + clientX: 0, + clientY: 0, + timeStamp: 0 + }, + + // Timeout used to send drag-move events + moveTimeout: null, + + // The last time a move event was fired + lastMoveEventTime: 0, + + // Whether to capture the next click event + clickCapture: false, + }; + + let dndObserverCallback = (mutations) => { + if (this._dnd.enabled !== this.dragAndDrop) { + this._dnd.enabled = this.dragAndDrop; + if (this._dnd.enabled) { + this.addEventListener("touchstart", this); + this.addEventListener("touchmove", this); + this.addEventListener("touchcancel", this); + this.addEventListener("touchend", this); + this.addEventListener("mousedown", this); + this.addEventListener("mousemove", this); + this.addEventListener("mouseup", this); + this.addEventListener("click", this, true); + this.addEventListener("contextmenu", this, true); + } else { + this.cancelDrag(); + this.removeEventListener("touchstart", this); + this.removeEventListener("touchmove", this); + this.removeEventListener("touchcancel", this); + this.removeEventListener("touchend", this); + this.removeEventListener("mousedown", this); + this.removeEventListener("mousemove", this); + this.removeEventListener("mouseup", this); + this.removeEventListener("click", this, true); + this.removeEventListener("contextmenu", this, true); + } + } + }; + this.dndObserver = new MutationObserver(dndObserverCallback); + this.dndObserver.observe(this, { + attributes: true, + attributeFilter: ["drag-and-drop"] + }); + + dndObserverCallback(null); + } + + get children() { //div.icon-container + return this._children.map((child) => { + return child.element; + }); + } + + get firstChild() { + return this._children.length ? + this._children[0].element : + null; + } + + get lastChild() { + const length = this._children.length; + return (length ? + this._children[length - 1].element : + null); + } + + get dragAndDrop() { + return (this.getAttribute("drag-and-drop") !== null); + } + + get dragAndDropTimeout() { + return this._dnd.delay; + } + + set dragAndDropTimeout(timeout) { + if (timeout >= 0) { + this._dnd.delay = timeout; + } else { + this._dnd.delay = DEFAULT_DND_TIMEOUT; + } + } + + realAppendChild() { + let proto = HTMLElement.prototype.appendChild; + let func = proto.apply(this, arguments); + return func; + } + + realRemoveChild() { + let proto = HTMLElement.prototype.removeChild; + let func = proto.apply(this, arguments); + return func; + } + + realInsertBefore() { + let proto = HTMLElement.prototype.insertBefore; + let func = proto.apply(this, arguments); + return func; + } + + realReplaceChild() { + let proto = HTMLElement.prototype.replaceChild; + let func = proto.apply(this, arguments); + return func; + } + + appendChild(element, callback) { + this.insertBefore(element, null, callback); + } + + removeChild(element, callback) { + let children = this._children; + let childToRemove = null; + + for (let child of children) { + if (child.element === element) { + childToRemove = child; + break; + } + } + + if (childToRemove === null) { + throw "removeChild called on unknown child"; + } + let that = this; + this.changeState(childToRemove, "removed", () => { + that.realRemoveChild(childToRemove.container); + that.realRemoveChild(childToRemove.master); + + // Find the child again. We need to do this in case the container was + // manipulated between removing the child and this callback being reached. + for (let i = 0, iLen = children.length; i < iLen; i++) { + if (children[i] === childToRemove) { + children.splice(i, 1); + break; + } + } + + if (callback) { + callback(); + } + + this.synchronise(); + }); + } + + replaceChild(newElement, oldElement, callback) { + if (!newElement || !oldElement) { + throw "replaceChild called with null arguments"; + } + + // Unparent the newElement if necessary (with support for gaia-container) + if (newElement.parentNode) { + newElement.parentNode.removeChild(newElement, () => { + this.replaceChild(newElement, oldElement, callback); + }); + if (newElement.parentNode) { + return; + } + } + + // Remove the old child and add the new one, but don't run the removed/ + // added state changes. + let children = this._children; + + for (let i = 0, iLen = children.length; i < iLen; i++) { + let oldChild = children[i]; + if (oldChild.element === oldElement) { + let newChild = new GaiaContainerChild(newElement); + this.realInsertBefore(newChild.container, oldChild.container); + this.realInsertBefore(newChild.master, oldChild.master); + this.realRemoveChild(oldChild.container); + this.realRemoveChild(oldChild.master); + this.children.splice(i, 1, newChild); + this.synchronise(); + + if (callback) { + callback(); + } + return; + } + } + + throw "removeChild called on unknown child"; + } + + /** + * Reorders the given element to appear before referenceElement. + */ + reorderChild(element, referenceElement, callback) { + if (!element) { + throw "reorderChild called with null element"; + } + + let children = this._children; + let child = null; + let childIndex = null; + let referenceChild = null; + let referenceChildIndex = null; + + for (let i = 0, iLen = children.length; i < iLen; i++) { + if (children[i].element === element) { + child = children[i]; + childIndex = i; + } else if (children[i].element === referenceElement) { + referenceChild = children[i]; + referenceChildIndex = i; + } + + if (child && (referenceChild || !referenceElement)) { + this.realRemoveChild(child.container); + this.realRemoveChild(child.master); + children.splice(childIndex, 1); + + if (referenceChild) { + this.realInsertBefore(child.container, referenceChild.container); + this.realInsertBefore(child.master, referenceChild.master); + } else { + (children.length === 0) ? + this.realAppendChild(child.container): + this.realInsertBefore(child.container, children[0].master); + this.realAppendChild(child.master); + } + + referenceChild ? + children.splice( + referenceChildIndex - (childIndex < referenceChildIndex) ? 1 : 0, + 0, child) : + children.splice(children.length, 0, child); + + this.synchronise(); + + if (callback) { + callback(); + } + return; + } + } + + throw child ? "reorderChild called on unknown reference element" : + "reorderChild called on unknown child"; + } + + insertBefore(element, reference, callback) { + let children = this._children; + let childToInsert = new GaiaContainerChild(element); + // console.log("childToInsert: ",childToInsert);// gaia-container-child + // console.log("children: ", children);//包括gaia-container-child 、icon-container等 + let referenceIndex = -1; + + if (reference !== null) { + for (let i = 0, iLen = children.length; i < iLen; i++) { + if (children[i].element === reference) { + referenceIndex = i; + break; + } + } + if (referenceIndex === -1) { + throw "insertBefore called on unknown child"; + } + } + + if (referenceIndex === -1) { + (children.length === 0) ? + this.realAppendChild(childToInsert.container): + this.realInsertBefore(childToInsert.container, children[0].master); + this.realAppendChild(childToInsert.master); + children.push(childToInsert); + } else { + this.realInsertBefore(childToInsert.container, + children[referenceIndex].container); + this.realInsertBefore(childToInsert.master, + children[referenceIndex].master); + children.splice(referenceIndex, 0, childToInsert); + } + + this.changeState(childToInsert, "added", callback); + this.synchronise(); + } + + /** + * Used to execute a state-change of a child that may possibly be animated. + * @state will be added to the child's class-list. If an animation starts that + * has the same name, that animation will complete and @callback will be + * called. Otherwise, the class will be removed and @callback called on the + * next frame. + */ + changeState(child, state, callback) { + // Check that the child is still attached to this parent (can happen if + // the child is removed while frozen). + if (child.container.parentNode !== this) { + return; + } + + // Check for a redundant state change. + if (child.container.classList.contains(state)) { + return; + } + + // Queue up state change if we're frozen. + if (this._frozen) { + this._pendingStateChanges.push( + this.changeState.bind(this, child, state, callback)); + return; + } + + let animStart = (e) => { + if (!e.animationName.endsWith(state)) { + return; + } + + child.container.removeEventListener("animationstart", animStart); + + window.clearTimeout(child[state]); + delete child[state]; + + let self = this; + child.container.addEventListener("animationend", function animEnd() { + child.container.removeEventListener("animationend", animEnd); + child.container.classList.remove(state); + if (callback) { + callback(); + } + }); + }; + + child.container.addEventListener("animationstart", animStart); + child.container.classList.add(state); + + child[state] = window.setTimeout(() => { + delete child[state]; + child.container.removeEventListener("animationstart", animStart); + child.container.classList.remove(state); + if (callback) { + callback(); + } + }, STATE_CHANGE_TIMEOUT); + } + + getChildOffsetRect(element) { + let children = this._children; + for (let i = 0, iLen = children.length; i < iLen; i++) { + let child = children[i]; + if (child.element === element) { + let top = child._lastMasterTop; + let left = child._lastMasterLeft; + let width = child._lastElementWidth; + let height = child._lastElementHeight; + + return { + top: top, + left: left, + width: width, + height: height, + right: left + width, + bottom: top + height + }; + } + } + + throw "getChildOffsetRect called on unknown child"; + } + + getChildFromPoint(x, y) { + let children = this._children; + for (let parent = this.parentElement; parent; parent = parent.parentElement) { + x += parent.scrollLeft - parent.offsetLeft; + y += parent.scrollTop - parent.offsetTop; + } + for (let i = 0, iLen = children.length; i < iLen; i++) { + let child = children[i]; + if (x >= child._lastMasterLeft && + y >= child._lastMasterTop && + x < child._lastMasterLeft + child._lastElementWidth && + y < child._lastMasterTop + child._lastElementHeight) { + return child.element; + } + } + + return null; + } + + cancelDrag() { + if (this._dnd.timeout !== null) { + clearTimeout(this._dnd.timeout); + this._dnd.timeout = null; + } + + if (this._dnd.moveTimeout !== null) { + clearTimeout(this._dnd.moveTimeout); + this._dnd.moveTimeout = null; + } + + if (this._dnd.active) { + this._dnd.child.container.classList.remove("dragging"); + this._dnd.child.container.style.position = "absolute"; + this._dnd.child.container.style.top = "0"; + this._dnd.child.container.style.left = "0"; + this._dnd.child.markDirty(); + this._dnd.child = null; + this._dnd.active = false; + this.synchronise(); + this._dnd.clickCapture = true; + this.dispatchEvent(new CustomEvent("drag-finish")); + } + } + + startDrag() { + if (!this.dispatchEvent(new CustomEvent("drag-start", { + cancelable: true, + detail: { + target: this._dnd.child.element, + pageX: this._dnd.start.pageX, + pageY: this._dnd.start.pageY, + clientX: this._dnd.start.clientX, + clientY: this._dnd.start.clientY + } + }))) { + return; + } + + this._dnd.active = true; + this._dnd.child.container.classList.add("dragging"); + this._dnd.child.container.style.position = "fixed"; + let rect = this.getBoundingClientRect(); + this._dnd.child.container.style.top = rect.top + "px"; + this._dnd.child.container.style.left = rect.left + "px"; + } + + continueDrag() { + if (!this._dnd.active) { + return; + } + + let left = this._dnd.child.master.offsetLeft + + (this._dnd.last.pageX - this._dnd.start.pageX); + let top = this._dnd.child.master.offsetTop + + (this._dnd.last.pageY - this._dnd.start.pageY); + this._dnd.child.container.style.transform = + "translate(" + left + "px, " + top + "px)"; + + if (this._dnd.moveTimeout === null) { + let delay = Math.max(0, DND_MOVE_THROTTLE - + (this._dnd.last.timeStamp - this._dnd.lastMoveEventTime)); + this._dnd.moveTimeout = setTimeout(() => { + this._dnd.moveTimeout = null; + this._dnd.lastMoveEventTime = this._dnd.last.timeStamp; + this.dispatchEvent(new CustomEvent("drag-move", { + detail: { + target: this._dnd.child.element, + pageX: this._dnd.last.pageX, + pageY: this._dnd.last.pageY, + clientX: this._dnd.last.clientX, + clientY: this._dnd.last.clientY + } + })); + }, delay); + } + } + + endDrag(event) { + if (this._dnd.active) { + let dropTarget = this.getChildFromPoint(this._dnd.last.clientX, + this._dnd.last.clientY); + + if (this.dispatchEvent(new CustomEvent("drag-end", { + cancelable: true, + detail: { + target: this._dnd.child.element, + dropTarget: dropTarget, + pageX: this._dnd.last.pageX, + pageY: this._dnd.last.pageY, + clientX: this._dnd.last.clientX, + clientY: this._dnd.last.clientY + } + }))) { + let children = this._children; + + if (dropTarget && dropTarget !== this._dnd.child.element) { + let dropChild = null; + let dropIndex = -1; + let childIndex = -1; + let insertBefore = true; + for (let i = 0, iLen = children.length; i < iLen; i++) { + if (children[i] === this._dnd.child) { + childIndex = i; + if (!dropChild) { + insertBefore = false; + } + } + + if (children[i].element === dropTarget) { + dropChild = children[i]; + dropIndex = i; + } + + if (dropIndex >= 0 && childIndex >= 0) { + break; + } + } + + if (dropIndex >= 0 && childIndex >= 0) { + // Default action, rearrange the dragged child to before or after + // the child underneath the touch/mouse point. + this.realRemoveChild(this._dnd.child.container); + this.realRemoveChild(this._dnd.child.master); + this.realInsertBefore(this._dnd.child.container, + insertBefore ? dropChild.container : + dropChild.container.nextSibling); + this.realInsertBefore(this._dnd.child.master, + insertBefore ? dropChild.master : + dropChild.master.nextSibling); + children.splice(dropIndex, 0, + children.splice(childIndex, 1)[0]); + this.dispatchEvent(new CustomEvent("drag-rearrange")); + } + } + } + } else if (this._dnd.timeout !== null) { + let handled = !this.dispatchEvent(new CustomEvent("activate", { + cancelable: true, + detail: { + target: this._dnd.child.element + } + })); + if (handled) { + event.stopImmediatePropagation(); + event.preventDefault(); + } + } + + this.cancelDrag(); + } + + handleEvent(event) { + switch (event.type) { + case "touchstart": + case "mousedown": + if (this._dnd.active || this._dnd.timeout) { + this.cancelDrag(); + break; + } + + if (event instanceof MouseEvent) { + this._dnd.start.pageX = event.pageX; + this._dnd.start.pageY = event.pageY; + this._dnd.start.clientX = event.clientX; + this._dnd.start.clientY = event.clientY; + // this._dnd.start.clientX = event.pageX; + // this._dnd.start.clientY = event.pageY; + } else { + this._dnd.start.pageX = event.touches[0].pageX; + this._dnd.start.pageY = event.touches[0].pageY; + this._dnd.start.clientX = event.touches[0].clientX; + this._dnd.start.clientY = event.touches[0].clientY; + } + + this._dnd.last.pageX = this._dnd.start.pageX; + this._dnd.last.pageY = this._dnd.start.pageY; + this._dnd.last.clientX = this._dnd.start.clientX; + this._dnd.last.clientY = this._dnd.start.clientY; + this._dnd.last.timeStamp = event.timeStamp; + + let target = event.target;//gaia-app-icon + + for (; target.parentNode !== this; target = target.parentNode) { + if (target === this || !target.parentNode) { + return; + } + } + + // Find the child + let children = this._children; + for (let child of children) { + if (child.container === target) { + this._dnd.child = child; + break; + } + } + + if (!this._dnd.child) { + return; + } + + if (this._dnd.delay > 0) { + this._dnd.timeout = setTimeout(() => { + this._dnd.timeout = null; + this.startDrag(); + }, this._dnd.delay); + } else { + this.startDrag(); + } + break; + + case "touchmove": + case "mousemove": + let pageX, pageY, clientX, clientY; + if (event instanceof MouseEvent) { + pageX = event.pageX; + pageY = event.pageY; + clientX = event.clientX; + clientY = event.clientY; + } else { + pageX = event.touches[0].pageX; + pageY = event.touches[0].pageY; + clientX = event.touches[0].clientX; + clientY = event.touches[0].clientY; + } + + if (this._dnd.timeout) { + if (Math.abs(pageX - this._dnd.start.pageX) > DND_THRESHOLD || + Math.abs(pageY - this._dnd.start.pageY) > DND_THRESHOLD) { + clearTimeout(this._dnd.timeout); + this._dnd.timeout = null; + } + } else if (this._dnd.active) { + event.preventDefault(); + this._dnd.last.pageX = pageX; + this._dnd.last.pageY = pageY; + this._dnd.last.clientX = clientX; + this._dnd.last.clientY = clientY; + this._dnd.last.timeStamp = event.timeStamp; + + this.continueDrag(); + } + break; + + case "touchcancel": + this.cancelDrag(); + break; + + case "touchend": + case "mouseup": + if (this._dnd.active) { + event.preventDefault(); + event.stopImmediatePropagation(); + } + this.endDrag(event); + break; + + case "click": + if (this._dnd.clickCapture) { + this._dnd.clickCapture = false; + event.preventDefault(); + event.stopImmediatePropagation(); + } + break; + + case "contextmenu": + if (this._dnd.active || this._dnd.timeout) { + event.stopImmediatePropagation(); + // event.preventDefault(); + } + break; + } + } + + /** + * Temporarily disables element position synchronisation. Useful when adding + * multiple elements to the container at the same time, or in quick + * succession. + */ + freeze() { + this._frozen = true; + } + + /** + * Enables element position synchronisation after freeze() has been called. + */ + thaw() { + if (this._frozen) { + this._frozen = false; + for (let callback of this._pendingStateChanges) { + callback(); + } + this._pendingStateChanges = []; + this.synchronise(); + } + } + + /** + * Synchronise positions between the managed container and all children. + * This is called automatically when adding/inserting or removing children, + * but must be called manually if the managed container is manipulated + * outside of these methods (for example, if style properties change, or + * if it's resized). + */ + synchronise() { + if (this._frozen) { + return; + } + + let child; + for (child of this._children) { + if (!this._dnd.active || child !== this._dnd.child) { + child.synchroniseMaster(); + } + } + + for (child of this._children) { + if (!this._dnd.active || child !== this._dnd.child) { + child.synchroniseContainer(); + } + } + } + + shadowStyle = ` + :host { + position: relative; + display: block; + } + ` + + template = ` + + + ` +} + +customElements.define("gaia-container", GaiaContainer); diff --git a/src/components/control-center/icondb.ts b/src/components/control-center/icondb.ts new file mode 100644 index 0000000..580f7b0 --- /dev/null +++ b/src/components/control-center/icondb.ts @@ -0,0 +1,88 @@ +export default class IconDB { + db: any; + DB_NAME = 'icon-db'; + DB_ORDER_STORE = 'order'; + DB_VERSION = 1; + + init() { + return new Promise((resolve: (value: void) => void, reject) => { + var req = window.indexedDB.open(this.DB_NAME, this.DB_VERSION); + req.onupgradeneeded = this.upgradeSchema; + req.onsuccess = () => { + this.db = req.result; + resolve(); + }; + req.onerror = (e) => { + console.error('Error opening icon db', e); + reject(e); + }; + }); + } + + upgradeSchema(e: any) { + var db = e.target.result; + var fromVersion = e.oldVersion; + if (fromVersion < 1) { + var store = db.createObjectStore(this.DB_ORDER_STORE, { keyPath: 'id' }); + store.createIndex('order', 'order', { unique: false }); + } + } + + set(data: any) { + return new Promise((resolve, reject) => { + var txn = this.db.transaction(this.DB_ORDER_STORE, + 'readwrite'); + for (var entry of data) { + if (!entry.id) { + continue; + } + + if (typeof entry.order !== 'undefined') { + txn.objectStore(this.DB_ORDER_STORE). + put({ id: entry.id, order: entry.order }); + } + } + txn.oncomplete = resolve; + txn.onerror = reject; + }); + } + + remove(id: string) { + return new Promise((resolve, reject) => { + var txn = this.db.transaction(this.DB_ORDER_STORE, 'readwrite'); + txn.objectStore(this.DB_ORDER_STORE).delete(id); + txn.oncomplete = resolve; + txn.onerror = reject; + }); + } + + getAll(onResult: Function) { + return new Promise((resolve: (value?: any) => void) => { + try { + + var txn = this.db.transaction(this.DB_ORDER_STORE, 'readonly'); + var orderStore = txn.objectStore(this.DB_ORDER_STORE); + var cursor = orderStore.index('order').openCursor(); + var results: any[] = []; + + cursor.onsuccess = (e: any) => { + var cursor = e.target.result; + if (cursor) { + var result = cursor.value; + console.log("ffff result: ", result); + if (onResult) { + onResult(result); + } + cursor.continue(); + } + }; + + txn.oncomplete = () => { + resolve(results); + }; + } catch (error) { + resolve("error"); + } + }); + } +} \ No newline at end of file diff --git a/src/components/control-center/mock_settings_observer.ts b/src/components/control-center/mock_settings_observer.ts new file mode 100644 index 0000000..5b35f23 --- /dev/null +++ b/src/components/control-center/mock_settings_observer.ts @@ -0,0 +1,88 @@ +interface Obj { + name?: string; + value?: any; + callback: (name: string, value: any)=> void; +} + +interface Pair { + name: string; + value: any; +} + + +export var settingsObserver = { + _keyvaluepairs: [] as Pair[], // 存放设定的设置键值对 + _observers: [] as Obj[], // 存放observe方法存入的 + + getValue: function(name: string) { + // return new Promise((resolve: (value: any) => void) => { + // resolve(true); + // }) + + return new Promise(resovle => { + let value; + this._keyvaluepairs.forEach((pair: any) => { + if(pair.name == name) { + value = pair.value; + } + }); + return resovle(value); + }) + }, + + observe: function(name: string, defaultValue: any, callback: (name: string, value: any)=> void, observeOnly: boolean) { + if (!observeOnly) { + this.getValue(name).then((value) => { + callback(name, value !== null && value !== void 0 ? value : defaultValue); // 重启前设置值为false或0时,重启后返回的值不应该为默认值 + }); + } + this._observers.push({ + name, + callback + }); + }, + + setValue: function(args: any[]) { + return new Promise((resovle: (value: void)=> void) => { + // 将传入的设置值更新/设置入_keyvaluepairs + args.forEach((pair: any) => { + let targetPair = this._keyvaluepairs.find((item: any) => { + return item.name == pair.name + }); + if (targetPair as any) { + // 在_keyvaluepairs根据pair.name找到了相同的设置值,更新键值对的value + (targetPair as any).value = pair.value; + } else { + // 在_keyvaluepairs不存在该设置值,新增键值对 + let newPair = { + name: pair.name, + value: pair.value + }; + this._keyvaluepairs.push(newPair); + } + + // 同时检查_observers中是否有该设置值变化时的回调 + this._observers.forEach(ob => { + ob.name = pair.name && ob.callback?.(pair.name, pair.value); + }); + + resovle(); + }); + }) + }, + + getBatch: function (names: string[]) { + return new Promise((resolve: ({}) => void) => { + let result = []; + for (let index = 0; index < names.length; index++) { + const name = names[index]; + let value = true; + result.push({name, value: value}); + } + + if (result.length == names.length) { + resolve(result); + } + }) + } +} \ No newline at end of file diff --git a/src/components/control-center/pull-down-menu.ts b/src/components/control-center/pull-down-menu.ts new file mode 100644 index 0000000..0007283 --- /dev/null +++ b/src/components/control-center/pull-down-menu.ts @@ -0,0 +1,128 @@ +import { html, css, LitElement, HTMLTemplateResult, nothing } from 'lit' +import { customElement, property, queryAssignedElements } from 'lit/decorators.js' +import { HeaderBarType } from './header-bar'; +import { IconItem, IconType } from './icon-item'; +import './icon-item'; +import './header-bar'; +export enum PullDownMenuType { + HEADER_WITH_TIME = 'header-with-time', + HEADER_WITH_DATE_TIME = 'header-with-date-time' +} + +@customElement('pull-down-menu') +export class PullDownMenu extends LitElement { + @property({type: PullDownMenuType}) type = ''; + @queryAssignedElements({flatten: true}) slotElements!: HTMLSlotElement[]; + getOnlyTime(): HTMLTemplateResult { + return html` +
+ + + + +
+ +
+
+ ` + } + + getTimeAndDate(): HTMLTemplateResult { + return html` +
+ + + +
+ +
+
+ ` + } + + render(): HTMLTemplateResult | typeof nothing { + switch (this.type) { + case PullDownMenuType.HEADER_WITH_TIME: + return this.getOnlyTime(); + case PullDownMenuType.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; + position: absolute; + left: 0px; + top: 0px; + } + + :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; + } + + 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; + } + ` + + handleEvent(event: Event) { + switch (event.type) { + case "click": + switch ((event.target as IconItem).icon) { + case "compose": + window.dispatchEvent(new CustomEvent("editor-action")); + break; + case "settings": + let activity = new WebActivity("configure", {}); + activity.start().then(() => {}, () => {}); + break; + } + break; + } + + } +} + +declare global { + interface HTMLElementTagNameMap { + 'pull-down-menu': PullDownMenu + } +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 48145d8..0226ecc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,6 +15,8 @@ import './components/toast/toast' import './components/picker/picker' import './components/overflowmenu/overflowmenu' import './components/slider/slider' +import './components/control-center/icon-item' +import './components/control-center/icons-one-line' @customElement('settings-app') export class SettingsApp extends LitElement { diff --git a/src/test/panels/control-center/control-center.ts b/src/test/panels/control-center/control-center.ts new file mode 100644 index 0000000..ca8ffb2 --- /dev/null +++ b/src/test/panels/control-center/control-center.ts @@ -0,0 +1,106 @@ +import {html, css, LitElement} from 'lit' +import {customElement, query} from 'lit/decorators.js' +import {IconItem, IconType} from "../../../components/control-center/icon-item" +import "../../../components/control-center/icon-item" +import {IconsOneLineType} from "../../../components/control-center/icons-one-line" +import {UlType} from "../../../components/ul/ul" +import {HeaderBarType} from "../../../components/control-center/header-bar" +import "../../../components/control-center/icons-one-line" +import "../../../components/ul/ul" +import "../../../components/control-center/header-bar" +import "../../../components/control-center/pull-down-menu" +import "../../../components/control-center/control-center-bar" +@customElement('panel-control-center') +export class PanelControlCenter extends LitElement { + @query('icon-item[icon="wifi-4"]') wifiIcon!: LitElement; + @query('icon-item[icon="crop"]') cropBtn!: LitElement; + @query('icon-item[icon="brightness"]') brightnessBtn!: LitElement; + render() { + return html` + +

带状态的图标,点击有背景色变化,绑定settingsObserver监测值

+ +
+

基本图标

+ +
+

基本图标,点击有背景色变化

+ +
+

基本图标,不带边框

+ +
+ + +

一行带状态的图标

+ + + + + +
+
+

一个box框,框内是由短竖线分隔开的基本图标

+ + + + + + +
+
+ + + +

header带时间

+ + + + +
+

header带时间/日期/星期

+ + + +
+
+ + + + + + + + + ` + + // return html` + // + // + // + // + + // ` + + // return html` + // + // + // ` + } + + static styles = css` + control-center-bar { + width: 1200px; + height: 1920px; + } + `; + handleEvent(event: Event) { + console.log("click on icon item: " + (event.target as IconItem).icon); + } +} + +declare global { + interface HTMLElementTagNameMap { + 'panel-control-center': PanelControlCenter + } +} diff --git a/src/test/panels/root.ts b/src/test/panels/root.ts index 5fca910..91305da 100644 --- a/src/test/panels/root.ts +++ b/src/test/panels/root.ts @@ -23,6 +23,7 @@ import './slider/slider' import './container/homescreen-container' import './toast/toast' import './picker/picker' +import './control-center/control-center' type SEID = string @@ -112,14 +113,6 @@ export class PanelRoot extends LitElement { href="#general" >
- -

+ +
Date: Thu, 22 Sep 2022 20:15:09 +0800 Subject: [PATCH 05/20] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../control-center/control-center-bar.ts | 2 +- src/components/control-center/icon-item.ts | 2 +- src/components/control-center/index.ts | 1 + src/components/control-center/package.json | 37 +++++++++++++++++++ src/components/control-center/tsconfig.json | 8 ++++ tsconfig.json | 2 +- 6 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 src/components/control-center/index.ts create mode 100644 src/components/control-center/package.json create mode 100644 src/components/control-center/tsconfig.json diff --git a/src/components/control-center/control-center-bar.ts b/src/components/control-center/control-center-bar.ts index 91f5355..268ed4d 100644 --- a/src/components/control-center/control-center-bar.ts +++ b/src/components/control-center/control-center-bar.ts @@ -97,7 +97,7 @@ export class ControlCenterBar extends LitElement { @property({type: IconItem}) airplaneMode!: IconItem; @property({type: IconItem}) data!: IconItem; @property({type: IconItem}) geolocation!: IconItem; - @property({type: Number}) timer!: number; + @property({type: Number}) timer!: NodeJS.Timeout; render(): HTMLTemplateResult { console.log("dddd render"); return html` diff --git a/src/components/control-center/icon-item.ts b/src/components/control-center/icon-item.ts index e0946e4..a3bdedd 100644 --- a/src/components/control-center/icon-item.ts +++ b/src/components/control-center/icon-item.ts @@ -16,7 +16,7 @@ export class IconItem extends LitElement { @property({type: Boolean}) bgchange = false; @property({type: String}) id = ''; @property({type: Boolean}) active = false; - @state({}) timer!: number; + @state({}) timer!: NodeJS.Timeout; @query('.more-info-icon') moreInfoIcon!: HTMLDivElement; @query('.icon-button') iconBtn!: HTMLDivElement; diff --git a/src/components/control-center/index.ts b/src/components/control-center/index.ts new file mode 100644 index 0000000..baeddc1 --- /dev/null +++ b/src/components/control-center/index.ts @@ -0,0 +1 @@ +export * from './control-center-bar.js' \ No newline at end of file diff --git a/src/components/control-center/package.json b/src/components/control-center/package.json new file mode 100644 index 0000000..88694f8 --- /dev/null +++ b/src/components/control-center/package.json @@ -0,0 +1,37 @@ +{ + "name": "@star-web-components/control-center-bar", + "version": "0.0.1", + "description": "", + "type": "module", + "main": "./index.js", + "module": "./index.js", + "exports": { + ".": { + "default": "./index.js" + }, + "./index": { + "default": "./index.js" + }, + "./icon-item.js": { + "default": "./icon-item.js" + }, + "./icons-one-line.js": { + "default": "./icons-one-line.js" + }, + "./header-bar.js": { + "default": "./header-bar.js" + }, + "./light-slider.js": { + "default": "./light-slider.js" + }, + "./pull-down-menu.js": { + "default": "./pull-down-menu.js" + }, + "./control-center-bar.js": { + "default": "./control-center-bar.js" + }, + "./package.json": "./package.json" + }, + "author": "", + "license": "ISC" +} diff --git a/src/components/control-center/tsconfig.json b/src/components/control-center/tsconfig.json new file mode 100644 index 0000000..d1d7546 --- /dev/null +++ b/src/components/control-center/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "composite": true, + "rootDir": "../../" + }, + "include": ["../../../global.d.ts", "*.ts"] +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index a32946f..f5c3bd9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -27,6 +27,6 @@ } ] }, - "include": ["global.d.ts", "src/**/*.ts"], + "include": ["src/**/*.ts"], "references": [{"path": "./tsconfig.node.json"}] } From a6b17562ee1a9a793417f106a74634fe3b459fc1 Mon Sep 17 00:00:00 2001 From: yajun Date: Sat, 24 Sep 2022 13:45:33 +0800 Subject: [PATCH 06/20] =?UTF-8?q?TASK:=20#110347=20=E5=AE=9E=E7=8E=B0overl?= =?UTF-8?q?ay=E4=B8=8Eoverflowmenu=E7=BB=84=E4=BB=B6=E7=9A=84=E8=9E=8D?= =?UTF-8?q?=E5=90=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/overflowmenu/overflowmenu.ts | 88 ++--- .../overflowmenu/overflowmenustyle.ts | 9 +- src/components/overlay/active-overlay.ts | 22 +- src/components/overlay/overlay-stack.ts | 42 ++- src/components/overlay/overlay.ts | 83 ----- src/components/overlay/overlaystyle.ts | 9 + .../panels/activeoverlay/activeoverlay.ts | 4 +- src/test/panels/overflowmenu/overflowmenu.ts | 316 +++++++++++------- 8 files changed, 283 insertions(+), 290 deletions(-) delete mode 100644 src/components/overlay/overlay.ts create mode 100644 src/components/overlay/overlaystyle.ts diff --git a/src/components/overflowmenu/overflowmenu.ts b/src/components/overflowmenu/overflowmenu.ts index e6a81cb..7604731 100644 --- a/src/components/overflowmenu/overflowmenu.ts +++ b/src/components/overflowmenu/overflowmenu.ts @@ -1,6 +1,7 @@ import {LitElement, html, HTMLTemplateResult, CSSResultArray} from 'lit' import {customElement, property, queryAssignedElements} from 'lit/decorators.js' import '../button/button' +import {OverlayStack} from '../overlay/overlay-stack' import {sharedStyles} from './overflowmenustyle' @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` - ` } diff --git a/src/components/overflowmenu/overflowmenustyle.ts b/src/components/overflowmenu/overflowmenustyle.ts index 685d996..62a7d3c 100644 --- a/src/components/overflowmenu/overflowmenustyle.ts +++ b/src/components/overflowmenu/overflowmenustyle.ts @@ -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; } diff --git a/src/components/overlay/active-overlay.ts b/src/components/overlay/active-overlay.ts index 3bdcd0b..2d7efe5 100644 --- a/src/components/overlay/active-overlay.ts +++ b/src/components/overlay/active-overlay.ts @@ -1,10 +1,15 @@ -import {LitElement, html, TemplateResult} from 'lit' +import {LitElement, html, TemplateResult, CSSResultArray} from 'lit' import {customElement, property} from 'lit/decorators.js' -import {OverlayOpenDetail, TriggerInteractions} from './overlay-types' +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 // 被点击的节点 @@ -26,12 +31,11 @@ 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 } private stealOverlayContent(element: HTMLElement) {} + // 位置更新函数 public updatePosition = () => { // 设置最大显示宽度和高度 @@ -45,7 +49,7 @@ 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) @@ -53,17 +57,17 @@ export class ActiveOverlay extends LitElement { 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' - // 边界判断 + // 边界判断:targetright指被点击的节点右边界,contentwidth表示将要显示的内容的宽度 const rightline = targetright + contentwidth > bodywidth ? true : false // 右侧越界条件 const bottomline = targetbottom + availableHeight > bodyheight ? true : false //下方越界条件 // 右下角边界 if (rightline && bottomline) { this.style.left = targetleft - (contentwidth - targetnode.offsetWidth) + 'px' - this.style.top = targettop - targetnode.offsetHeight + 'px' + this.style.top = targettop - contentheight - targetnode.offsetHeight + 'px' return } else if (rightline) { // 右侧边界 @@ -73,7 +77,7 @@ export class ActiveOverlay extends LitElement { } else if (bottomline) { // 下侧边界 this.style.left = targetleft + 'px' - this.style.top = targettop - contentheight + 'px' + this.style.top = targettop - contentheight - targetnode.offsetHeight + 'px' return } else { // 正常情况 diff --git a/src/components/overlay/overlay-stack.ts b/src/components/overlay/overlay-stack.ts index 3b2e954..8368e7d 100644 --- a/src/components/overlay/overlay-stack.ts +++ b/src/components/overlay/overlay-stack.ts @@ -9,18 +9,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, + }) + ) + } + }) } /** @@ -35,10 +63,14 @@ export class OverlayStack { const placeholder = srcroot?.lastChild as Node // 获取overlay中的内容 const content = closeactiveoverlay.restoreContent as HTMLElement + console.log(content.offsetWidth); // 替换子节点 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 } } diff --git a/src/components/overlay/overlay.ts b/src/components/overlay/overlay.ts deleted file mode 100644 index 180f6f7..0000000 --- a/src/components/overlay/overlay.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { OverlayStack } from './overlay-stack' -import { OverlayOptions, TriggerInteractions } from './overlay-types' - -export interface OverlayDisplayQueryDetail { - overlayRootName?: string - overlayRootElement?: HTMLElement - overlayContentTipElement?: HTMLElement -} - -export class Overlay { - // 基础属性 - private isOpen = false - private overlayElement: HTMLElement - private owner: HTMLElement - private interaction: TriggerInteractions - - private static overlayStack = new OverlayStack() - - /** - * 初始化构造参数 - * @param owner 用于定位overlay显示位置的父元素节点 - * @param interaction 引起overlay显示的交互类型 - * @param overlayElement 用于显示overlay的节点 - */ - constructor( - owner: HTMLElement, - interaction: TriggerInteractions, - overlayElement: HTMLElement - ) { - this.owner = owner - this.overlayElement = overlayElement - this.interaction = interaction - } - - public static async open( - owner: HTMLElement, - interaction: TriggerInteractions, - overlayElement: HTMLElement, - options: OverlayOptions - ): Promise<() => void> { - const overlay = new Overlay(owner, interaction, overlayElement) - await overlay.open(options) - return (): void => { - overlay.close() - } - } - - public static update(): void { - // 自定义事件 - const overlayUpdateEvent = new CustomEvent('star-update-overlays', { - bubbles: true, - composed: true, - cancelable: true, - }) - // 触发事件 - document.dispatchEvent(overlayUpdateEvent) - } - - public async open({ - offset = 0, - placement = 'top', - root, - }: OverlayOptions): Promise { - /* c8 ignore next */ - if (this.isOpen) return true - - await Overlay.overlayStack.openOverlay({ - content: this.overlayElement, - offset: offset, - placement: placement, - trigger: this.owner, - interaction: this.interaction, - root, - }) - this.isOpen = true - return true - } - - // 关闭overlay - public close(): void { - Overlay.overlayStack.closeOverlay(this.overlayElement) - } -} diff --git a/src/components/overlay/overlaystyle.ts b/src/components/overlay/overlaystyle.ts new file mode 100644 index 0000000..d1dc490 --- /dev/null +++ b/src/components/overlay/overlaystyle.ts @@ -0,0 +1,9 @@ +import {css, CSSResult} from 'lit' +export const sharedStyles: CSSResult = css` + :host { + display: block; + } + div { + max-width: 200px; + } +` diff --git a/src/test/panels/activeoverlay/activeoverlay.ts b/src/test/panels/activeoverlay/activeoverlay.ts index 730f28c..84e7f20 100644 --- a/src/test/panels/activeoverlay/activeoverlay.ts +++ b/src/test/panels/activeoverlay/activeoverlay.ts @@ -40,7 +40,7 @@ export class PanelActiveOverlay extends LitElement { -
+
@@ -50,7 +50,7 @@ export class PanelActiveOverlay extends LitElement {
-
+
diff --git a/src/test/panels/overflowmenu/overflowmenu.ts b/src/test/panels/overflowmenu/overflowmenu.ts index 2465527..8f431ba 100644 --- a/src/test/panels/overflowmenu/overflowmenu.ts +++ b/src/test/panels/overflowmenu/overflowmenu.ts @@ -1,10 +1,9 @@ -import {html, LitElement, css} from 'lit' +import {html, LitElement, css, CSSResultArray} from 'lit' import {customElement, property} 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,213 @@ 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` -
- - - - - - - - - - - - - - - - - - - - + + + - + + + + + +
+ + + + + + + + + + + + + ` } - 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); + } + `, + ] + } } declare global { From 861ad2a586a8a9ddd0859ef7b6898559fac04085 Mon Sep 17 00:00:00 2001 From: wurou Date: Fri, 23 Sep 2022 16:04:35 +0800 Subject: [PATCH 07/20] =?UTF-8?q?=E7=BB=86=E5=88=86=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- global.d.ts | 9 - .../brightness-slider/brightness-slider.ts | 193 ++++ src/components/brightness-slider/index.ts | 1 + src/components/brightness-slider/package.json | 22 + .../tsconfig.json | 4 +- src/components/control-center/README.md | 93 -- .../control-center/control-center-bar.ts | 926 ------------------ .../control-center/gaia-container-child.js | 103 -- .../control-center/gaia-container.js | 789 --------------- src/components/control-center/header-bar.ts | 289 ------ src/components/control-center/icon-item.ts | 318 ------ src/components/control-center/icondb.ts | 88 -- .../control-center/icons-one-line.ts | 137 --- src/components/control-center/index.ts | 1 - src/components/control-center/light-slider.ts | 153 --- .../control-center/mock_settings_observer.ts | 88 -- src/components/control-center/package.json | 37 - .../control-center/pull-down-menu.ts | 128 --- .../drop-down-menu/drop-down-menu.ts | 173 ++++ src/components/drop-down-menu/index.ts | 1 + src/components/drop-down-menu/interface.ts | 9 + src/components/drop-down-menu/package.json | 22 + src/components/drop-down-menu/tsconfig.json | 13 + src/components/header-bar/header-bar.ts | 327 +++++++ src/components/header-bar/index.ts | 1 + src/components/header-bar/package.json | 22 + src/components/header-bar/tsconfig.json | 8 + .../icon-control-bar-group.ts | 170 ++++ .../icon-control-bar-group/index.ts | 1 + .../icon-control-bar-group/package.json | 22 + .../icon-control-bar-group/tsconfig.json | 8 + .../icon-control-bar/icon-control-bar.ts | 426 ++++++++ src/components/icon-control-bar/index.ts | 1 + src/components/icon-control-bar/interface.ts | 9 + src/components/icon-control-bar/package.json | 22 + src/components/icon-control-bar/tsconfig.json | 8 + src/index.ts | 4 +- .../panels/control-center/control-center.ts | 198 ++-- src/test/panels/root.ts | 4 - 39 files changed, 1570 insertions(+), 3258 deletions(-) delete mode 100644 global.d.ts create mode 100644 src/components/brightness-slider/brightness-slider.ts create mode 100644 src/components/brightness-slider/index.ts create mode 100644 src/components/brightness-slider/package.json rename src/components/{control-center => brightness-slider}/tsconfig.json (71%) delete mode 100644 src/components/control-center/README.md delete mode 100644 src/components/control-center/control-center-bar.ts delete mode 100644 src/components/control-center/gaia-container-child.js delete mode 100644 src/components/control-center/gaia-container.js delete mode 100644 src/components/control-center/header-bar.ts delete mode 100644 src/components/control-center/icon-item.ts delete mode 100644 src/components/control-center/icondb.ts delete mode 100644 src/components/control-center/icons-one-line.ts delete mode 100644 src/components/control-center/index.ts delete mode 100644 src/components/control-center/light-slider.ts delete mode 100644 src/components/control-center/mock_settings_observer.ts delete mode 100644 src/components/control-center/package.json delete mode 100644 src/components/control-center/pull-down-menu.ts create mode 100644 src/components/drop-down-menu/drop-down-menu.ts create mode 100644 src/components/drop-down-menu/index.ts create mode 100644 src/components/drop-down-menu/interface.ts create mode 100644 src/components/drop-down-menu/package.json create mode 100644 src/components/drop-down-menu/tsconfig.json create mode 100644 src/components/header-bar/header-bar.ts create mode 100644 src/components/header-bar/index.ts create mode 100644 src/components/header-bar/package.json create mode 100644 src/components/header-bar/tsconfig.json create mode 100644 src/components/icon-control-bar-group/icon-control-bar-group.ts create mode 100644 src/components/icon-control-bar-group/index.ts create mode 100644 src/components/icon-control-bar-group/package.json create mode 100644 src/components/icon-control-bar-group/tsconfig.json create mode 100644 src/components/icon-control-bar/icon-control-bar.ts create mode 100644 src/components/icon-control-bar/index.ts create mode 100644 src/components/icon-control-bar/interface.ts create mode 100644 src/components/icon-control-bar/package.json create mode 100644 src/components/icon-control-bar/tsconfig.json diff --git a/global.d.ts b/global.d.ts deleted file mode 100644 index 67bf093..0000000 --- a/global.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -interface WebActivity { - start(): Promise; - cancel(): void; -} - -declare var WebActivity: { - prototype: WebActivity; - new(name: string, data?: any): WebActivity; -}; \ No newline at end of file diff --git a/src/components/brightness-slider/brightness-slider.ts b/src/components/brightness-slider/brightness-slider.ts new file mode 100644 index 0000000..ebd7f84 --- /dev/null +++ b/src/components/brightness-slider/brightness-slider.ts @@ -0,0 +1,193 @@ +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` +
+
+
+ ` + } + + 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 (max-width: 1200px) { + .sliderBar { + border-radius: 16px; + } + + .progress { + border-radius: 16px; + } + + .sliderBar::before { + width: 48px; + height: 48px; + left: 29px; + top: 29px; + font-size: 48px; + } + } + + @media screen and (max-width: 600px) { + .sliderBar { + border-radius: 8px; + } + + .progress { + border-radius: 8px; + } + + .sliderBar::before { + width: 24px; + height: 24px; + left: 14px; + top: 24px; + 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 + } +} diff --git a/src/components/brightness-slider/index.ts b/src/components/brightness-slider/index.ts new file mode 100644 index 0000000..3e3525d --- /dev/null +++ b/src/components/brightness-slider/index.ts @@ -0,0 +1 @@ +export * from './brightness-slider.js' diff --git a/src/components/brightness-slider/package.json b/src/components/brightness-slider/package.json new file mode 100644 index 0000000..1b62277 --- /dev/null +++ b/src/components/brightness-slider/package.json @@ -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" +} diff --git a/src/components/control-center/tsconfig.json b/src/components/brightness-slider/tsconfig.json similarity index 71% rename from src/components/control-center/tsconfig.json rename to src/components/brightness-slider/tsconfig.json index d1d7546..4ba3e2c 100644 --- a/src/components/control-center/tsconfig.json +++ b/src/components/brightness-slider/tsconfig.json @@ -4,5 +4,5 @@ "composite": true, "rootDir": "../../" }, - "include": ["../../../global.d.ts", "*.ts"] -} \ No newline at end of file + "include": ["*.ts"] +} diff --git a/src/components/control-center/README.md b/src/components/control-center/README.md deleted file mode 100644 index 2c59bb4..0000000 --- a/src/components/control-center/README.md +++ /dev/null @@ -1,93 +0,0 @@ -# icon-item -用于显示下拉控件栏中的icon。 -分为两类: -`IconType.BASE`类型: -须传入`icon`(用于图标内容),`bgchange`(用于点击图标时的样式变化),`bgchange`未传参时,默认图标点击时不改变图标背景 -`IconType.WITH_STATE`类型: -须传入`icon`(用于图标内容),`stateDesc`(用于蓝牙、wifi状态显示) -可选参数`settingsKey`: 用于settingsObsever。 - -`IconType.BASE_WITHOUT_BORDER`类型: -须传入`icon`(用于图标内容),图标不带边框。 - - -示例: -```html - - - - - - - -``` - - -# icons-online -对`icon-item`的进一步组合、封装。 -分为两类: -`IconsOneLineType.BASE`类型: -需传入`count`,为所插入插槽元素的个数,默认数值为4; -需插入插槽元素,显示为一个box框,框内是由短竖线分隔开的基本图标。 -`IconsOneLineType.WITH_STATE`类型: -需传入`count`,为所插入插槽元素的个数,默认数值为2; -需插入插槽元素,显示为一行带状态的图标。 - -示例: -```html - - - - - - - - - - - - -``` - - -# header-bar -用于显示下拉控件栏、下拉通知栏最顶部的header。 -分为两类: -`HeaderBarType.ONLY_TIME`类型: -header中显示时间和任意图标,需插入插槽元素 -`HeaderBarType.DATE_TIME`类型: -header中显示日期、星期、时间和任意图标,需插入插槽元素 -示例: -```html - - - - - - - - - - -``` - -# control-center -下拉控件栏组件。 -包括`header-bar`和中间控件部分。中间控件部分由slot元素插入。 - -```html - -
- -
-``` - -# control-center-bar -`control-center`实例 - -说明: 因现有下拉栏顶部的状态栏是采用`background-image:-moz-element(#statusbar-icons)`方式获取,所以使用`slot`获取。 -```html - -
-
-``` \ No newline at end of file diff --git a/src/components/control-center/control-center-bar.ts b/src/components/control-center/control-center-bar.ts deleted file mode 100644 index 268ed4d..0000000 --- a/src/components/control-center/control-center-bar.ts +++ /dev/null @@ -1,926 +0,0 @@ -import { html, css, LitElement, HTMLTemplateResult } from 'lit' -import { customElement, property, query } from 'lit/decorators.js' -import { settingsObserver as settingsObserver} from "./mock_settings_observer"; -import { IconsOneLine, IconsOneLineType } from './icons-one-line' -import { IconItem, IconType } from './icon-item'; -import { PullDownMenuType } from './pull-down-menu'; -import './icons-one-line'; -import './icon-item'; -import './pull-down-menu'; -import './light-slider'; -import "./gaia-container"; -// import "../gaia_dialog/gaia-dialog-list.js"; -// import WifiContext from "../../js/utils/origin/wifi/wifi_context.js"; -import IconDB from "./icondb"; -import { LightSlider } from './light-slider'; -// import WifiNetworkList from "../../js/utils/origin/wifi/wifi_network_list.js"; -// import WifiItem from "../../js/utils/origin/wifi/wifi_item.js"; -// import BtTemplateFactory from '../../js/utils/origin/bluetooth/bt_template_factory.js'; -// import ListView from '../../js/utils/origin/mvvm/list_view.js'; -// import BtContext from '../../js/utils/origin/bluetooth/bluetooth_context.js'; -// import BluetoothItem from "../../js/utils/origin/bluetooth/bluetooth_item.js"; -const WIFIENABLED = "wifi.enabled"; -// const BLUETOOTHENABLED = "bluetooth.enabled"; -// const BRIGHTNESS = "screen.brightness"; -// const AUTOBRIGHTNESS = "screen.automatic-brightness"; -const GEOLOCATIONENABLED = "geolocation.enabled"; -const WIFI_HOTSPOT = "tethering.wifi.enabled"; -const LOCK_SCREEN_KEY = "screen.orientation.lock"; -// const AUDIO_VOLUME_NOTIFICATION = "audio.volume.notification"; -// const VIBRATEENABLED = "vibration.enabled"; -// const WIFI_SSID_KEY = "tethering.wifi.ssid"; -// const WIFI_SECURITY_KEY = "tethering.wifi.security.type"; -// const WIFI_PASSWORD_KEY = "tethering.wifi.security.password"; - -let _debug = false; -_debug = true; -let debug = function (msg?: string | any) { - console.log(msg); - }; -if (_debug) { - debug = function btp_debug(msg) { - console.log('--> [ControlCenter]: ' + msg); - }; -} - -@customElement('control-center-bar') -export class ControlCenterBar extends LitElement { - @query('gaia-container') icons!: any; - @query('gaia-dialog-list#wifi-list') wifiListDialog!: any; - @query('gaia-dialog-list#bluetooth-list') bluetoothListDialog!: any; - @query('icons-one-line[type="base"]') cameraPhoto!: IconsOneLine; - @query('icon-item[icon="bluetooth"]') bluetooth!: IconItem; - @query('icon-item[icon="wifi-4"]') wifi!: IconItem; - @query('icon-item[icon="brightness"]') autoBrightness!: IconItem; - @query('#screen-brightness') brightness!: LightSlider; - @property({type: IconDB}) icondb = new IconDB(); - @property() settings = { - wifi: false, - airplaneMode: false, - data: false, - bluetooth: false, - wifihotspot: false, - geolocation: false, - screenLock: false, - brightness: 1, - autoBrightness: false, - }; - @property({type: Number}) iconsLeft = 0; - @property({type: Number}) iconsRight = 0; - @property({type: Boolean}) airplaneModeSwitching = false; - @property({type: Number}) lastWindowWidth = window.innerWidth; - @property({type: Number}) lastWindowHeight = window.innerHeight; - @property() pairedDevicesListView = null; - @property({type: String}) iconOnlyElements = ["data", "airplane-mode", "geolocation", "wifi-hotspot", "dark-mode", "sound", "auto-rotate", "addons"]; - @property({type: []}) startupOrder !: any[]; - @property({type: Boolean}) draggable = false; - @property({type: Number}) minRange = 10; // 最小滑动距离 - @property() touchAction = { // 触摸开始落点 - start: { - pageX: 0, - pageY: 0, - clientX: 0, - clientY: 0 - }, - - last: { // 触摸结束落点 - pageX: 0, - pageY: 0, - clientX: 0, - clientY: 0 - } - } - @property({type: Number}) preVolumeValue = 1; - @property({type: IconItem}) wifiHotspot!: IconItem; - @property({type: IconItem}) autoRotate!: IconItem; - @property({type: IconItem}) sound!: IconItem; - @property({type: IconItem}) airplaneMode!: IconItem; - @property({type: IconItem}) data!: IconItem; - @property({type: IconItem}) geolocation!: IconItem; - @property({type: Number}) timer!: NodeJS.Timeout; - render(): HTMLTemplateResult { - console.log("dddd render"); - return html` - - - - - - -
- -
-
- - -
- - - - - - -
-
-
-
- - -
- ` - } - - static styles = css` - :host { - --background-color-lm: rgba(217, 217, 217, 0.65); - --background-color-dm: rgba(89, 89, 89, 0.68); - width: inherit; - height: inherit; - display: block; - background: var(--background-color-lm); - backdrop-filter: blur(120px); - transform: translateY(0); - transition: transform 300ms ease, visibility 300ms; - visibility: hidden; - } - - :host([open]) { - visibility: visible; - } - - ::slotted(div) { - background-image: -moz-element(#statusbar-icons); - background-repeat: no-repeat; - width: 100%; - height: 3rem; - filter: none; - // background-position: 600px center; - background-position: right; - } - - pull-down-menu { - // width: 100%; - // height: calc(100% - 3rem) - position; absolute; - top: 140px; - left: 628px; - width: 512px; - } - - icons-one-line { - width: 512px; - height: 112px; - } - - icons-one-line[type='with-state'] { - position: relative; - top: 40px; - } - - .icon-section { - // width: inherit; - // height: 238px; - - width: 540px; - height: 268px; - position: relative; - top: 72px; - - left: -9px; - } - - gaia-container { - width: 100%; - height: 100%; - display: flex; - flex-flow: row wrap; - align-content: flex-start; - box-sizing: padding-box; - } - - .icon-container { - // height: 119px; - // width: 128px; - height: 134px; - width: 135px; - position: relative; - display: inline-block; - } - - .icon-container > icon-item { - position: absolute; - top: 0; - left: 0; - transition: transform 0.2s, opacity 0.2s; - transform: translate(12px, 7.5px); - } - - gaia-container:not(.loading) > .gaia-container-child:not(.added):not(.dragging) { - transition: transform 0.2s; - } - - gaia-container > .gaia-container-child.dragging { - z-index: 1; - will-change: transform; - } - - .brightness { - display: flex; - align-items: center; - justify-content: space-between; - - position: relative; - // top: 104px; - top: 89px; //104-15 - } - - #screen-brightness { - width: 378px; - height: 104px; - } - - icons-one-line[type='base'] { - position: relative; - top: 136px; - } - - #toggle-button { - width: 88px; - height: 10px; - - background: rgba(255, 255, 255, 0.35); - border-radius: 16px; - } - - .media-player { // 占位隐藏 - // width: inherit; - // height: 134px; - // position: relative; - // top: 200px; - // border: 1px solid white; - - // height: 67px; - // top: 100px; - // border: 0.5px solid white; - } - - .button-wrapper { - position: relative; - top: 168px; - display: flex; - justify-content: center; - } - - :host([dark-mode]) { - width: inherit; - height: inherit; - display: block; - background: var(--background-color-dm); - backdrop-filter: blur(120px); - } - - [disabled="true"] { - pointer-events: none; - opacity: 0.2; - } - ` - - firstUpdated() { - this.iconAdded().then(() => { - this.monitorWifiChange(); - this.monitorTetheringStatusChange(); - window.addEventListener("send-to-control-center", this.toggleActive.bind(this)); - this.monitorAirplaneModeChange(); - this.monitorBluetoothChange(); - - this.cameraPhoto.addEventListener("click", this.handleCamera.bind(this)); - // ["click", "touchstart", "touchend", "drag-end", - ["touch-start", "touchend", "drag-end", - "drag-rearrange", "drag-finish"].forEach(eventName => { - this.icons.addEventListener(eventName, this.handleContainerEvent.bind(this)) - }); - - - [this.wifiListDialog, this.bluetoothListDialog].forEach(dialog => { - dialog.addEventListener("more-settings", this.handleNavigate.bind(this)); - }); - - window.addEventListener("touchstart", this.handleStart.bind(this)); - window.addEventListener("touchmove", this.handleMove.bind(this)); - window.addEventListener("editor-action", this.handleEditor.bind(this)); - window.addEventListener("icon-item-click", this.handleIconButtonClick.bind(this)); - window.addEventListener("more-info-action", this.showListDialog.bind(this)); - }); - - } - - iconAdded() { - return new Promise((resolve: (value: void) => void) => { - this.icondb.init().then(() => { - // return this.icondb.getAll((result: any) => { - // console.log("iconAdded icondb getAll: ", result); - - // if (result) { - // let id = result.id; - // this.startupOrder.push(id); - // this.iconOnlyElements = this.startupOrder; - // } - // return Promise.resolve(); - // }); - }).then(() => { - this.addIcon(); - resolve(); - }); - }); - } - - toggleActive() { - if (this.autoBrightness.active == this.settings.autoBrightness) { - this.autoBrightness.activeOrInactive(!this.settings.autoBrightness); - } - if (this.geolocation.active == this.settings.geolocation) { - this.geolocation.activeOrInactive(!this.settings.geolocation); - } - - if (this.wifiHotspot.active == this.settings.wifihotspot) { - this.wifiHotspot.activeOrInactive(!this.settings.wifihotspot); - } - - if (this.autoRotate.active == this.settings.screenLock) { - - this.autoRotate.activeOrInactive(!this.settings.screenLock); - } - - if (this.brightness.value == this.settings.brightness) { - - // this.brightness.value = this.settings.brightness; - this.brightness.setValue(this.settings.brightness); - } - } - - - monitorWifiChange() { - // WifiItem(this.wifiStatusText).enabled = true; - // WifiItem(this.wifi.stateDesc).enabled = true; - window.addEventListener("wifi-enabled", this.handleWifiChange.bind(this)); - window.addEventListener("wifi-disabled", this.handleWifiChange.bind(this)); - window.addEventListener("wifi-statuschange", this.handleWifiChange.bind(this)); - } - - monitorAirplaneModeChange() { - - window.addEventListener("send-airplaneMode-status", (evt: any) => { - let value = evt.detail.value; - this.airplaneModeSwitching = false; - let target = this.airplaneMode; // 飞行模式element - - switch (value) { - case "enabled": - this.setSettingEnabled("airplaneMode", true); - target.activeOrInactive(false); // addClass - this.wifiHotspot.setAttribute("disabled", "true"); // 飞行模式下禁用wifi热点和移动数据 - this.data.setAttribute("disabled", "true"); - if (this.data.active) { - this.data.activeOrInactive(true); - } - break; - case "disabled": - this.setSettingEnabled("airplaneMode", false); - target.activeOrInactive(true);// removeClass - this.wifiHotspot.removeAttribute("disabled"); - this.data.removeAttribute("disabled"); - if (!this.data.active) { - this.data.activeOrInactive(false); - } - break; - case "enabling": - this.airplaneModeSwitching = true; - break; - case "disabling": - this.airplaneModeSwitching = true; - break; - } - }); - } - - monitorBluetoothChange() { - window.addEventListener("bluetooth-enabled", this.handleBlueToothChange.bind(this)); - window.addEventListener("bluetooth-disabled", this.handleBlueToothChange.bind(this)); - } - - monitorTetheringStatusChange() { - window.addEventListener("tethering-status-change", evt => { - settingsObserver.getValue(WIFIENABLED).then(wifiEnabled => { - if (!wifiEnabled && (evt as any).detail === 0) { // wifi打开状态下,wifi热点打开再关闭, 应恢复wifi - settingsObserver.setValue([{name: WIFIENABLED, value: true }]); - } - }); - }); - } - - setSettingEnabled(settingKey: string, enabled: any) { - (this.settings as any)[settingKey] = enabled; - } - - onPairedDeviceItemClick(deviceItem: any) { - debug('onPairedDeviceItemClick(): deviceItem.address = ' + - deviceItem.address); - debug('onPairedDeviceItemClick(): deviceItem.paired = ' + - deviceItem.paired); - } - - handleWifiChange(evt: Event) { - evt.preventDefault(); - switch (evt.type) { - case "wifi-enabled": - // debug("handleWifiChange wifi-enabled!!"); - this.setSettingEnabled("wifi", true); - this.wifi.activeOrInactive(false); - this.scan(); - break; - case "wifi-disabled": - // debug("handleWifiChange wifi-disabled!!"); - this.setSettingEnabled("wifi", false); - this.wifi.activeOrInactive(true); - /* - WifiItem(this.wifi.stateDesc).enabled = false; - */ - break; - case "wifi-statuschange": - // debug("handleWifiChange wifi-statuschange!!"); - let scanStates = new Set(["connected", "connectingfailed", "disconnected"]); - if (scanStates.has((evt as any).detail.event.status)) { - this.scan(); - } - break; - } - } - - handleBlueToothChange(event: Event) { - // event.preventDefault(); - switch (event.type) { - case "bluetooth-enabled": - this.setSettingEnabled("bluetooth", true); - this.bluetooth.activeOrInactive(false); - - // let pairedDeviceTemplate = - // BtTemplateFactory("paired", this.onPairedDeviceItemClick.bind(this)); - - // this.pairedDevicesListView = ListView(this.bluetoothListDialog, - // BtContext.getPairedDevices(), - // pairedDeviceTemplate); - (this.pairedDevicesListView as any).enabled = true; - break; - case "bluetooth-disabled": - this.setSettingEnabled("bluetooth", false); - this.bluetooth.activeOrInactive(true); - if (this.pairedDevicesListView) { - (this.pairedDevicesListView as any).enabled = false; - } - break; - } - } - - handleEditor() { - let dragAndDrop = this.icons.getAttribute("drag-and-drop"); - this.icons.setAttribute("drag-and-drop", (!dragAndDrop).toString()); - this.draggable = !dragAndDrop; - } - - handleSettings() { - let activity = new WebActivity("configure", {}); - activity.start().then(() => {}, () => {}); - } - - showListDialog(event: Event) { - let target = (event as any).detail; - switch (target) { - case "wifi-4": - this.wifiListDialog.open(event); - break; - case "bluetooth": - this.bluetoothListDialog.open(event); - break; - } - } - - handleIconButtonClick(event: any) { - let target = event.detail.target; - console.log("handleIconButtonClick target: ", target); - console.log("handleIconButtonClick event.detail: ", event.detail); - let isContainer = target.parentElement?.parentElement?.parentElement?.id == "icon-only"; - if (isContainer && this.draggable) { - return; - } - - let isActive = event.detail.isActive; - let id = event.detail.id; - - switch (id) { - case "wifi": - if (this.airplaneModeSwitching) { - return; - } - this.fireEvent(id, !isActive); - break; - case "bluetooth": - if (this.airplaneModeSwitching) { - return; - } - let enabled = this.settings.bluetooth; - var toggleb = enabled ? - "request-disable-bluetooth" : - "request-enable-bluetooth"; - this.dispatchEvent( - new CustomEvent(toggleb, { - bubbles: true, - composed: true, - }) - ); - break; - case "screen-auto-brightness": - this.fireEvent(id, !isActive); - break; - case "airplane-mode": - if (this.airplaneModeSwitching) { - return; - } - - var toggle = isActive ? - "request-airplane-mode-disable" : - "request-airplane-mode-enable"; - this.dispatchEvent( - new CustomEvent(toggle, { - bubbles: true, - composed: true, - }) - ); - if (isActive) { - this.wifiHotspot.removeAttribute("disabled"); - this.data.removeAttribute("disabled"); - } else { - this.wifiHotspot.setAttribute("disabled", "true"); - this.data.setAttribute("disabled", "true"); - } - break; - case "geolocation": - this.fireEvent(id, !isActive); - break; - case "wifi-hotspot": - if (this.airplaneModeSwitching) { - return; - } - this.fireEvent(id, !isActive); - break; - case "dark-mode": - break; - - case "sound": - if (this.sound.icon == "alarm") { - this.sound.icon = "mute"; - this.fireEvent("mute", 0); - } else if (this.sound.icon == "mute") { - this.sound.icon = "alarm"; - this.fireEvent("alarm", this.preVolumeValue); - } - break; - - case "auto-rotate": - this.fireEvent(id, !isActive); - break; - } - } - - handleIconButtonPress(event: any) { - let target = event.detail.target; - let isContainer = target.parentElement?.parentElement?.parentElement?.id == "icon-only"; - - if (isContainer && this.draggable) { - return; - } - - if (target.className == "icon-container") { - target = target.children[0]; - } - - if (target.getAttribute("disabled") === "true") { - return; - } - - this.close(); - let section = target.id; - this.timer = setTimeout(() => { - let activity = new WebActivity("moz_configure_window", { - data: { - target: "device", - section: section - } - }); - activity.start(); - }, 800); - } - - handleIconButtonPressEnd(event: any) { - let target = event.target; - let isContainer = target.parentElement?.parentElement?.parentElement?.id == "icon-only"; - if (isContainer && this.draggable) { - return; - } - clearTimeout(this.timer); - } - - handleNavigate(event: any) { - let section = event.target.id; - let activity = new WebActivity("moz_configure_window", { - data: { - target: "device", - section: section - } - }); - activity.start(); - } - - handleCamera(event: any) { - let target = event.target.icon; - switch (target) { - case "crop": - this.dispatchEvent( - new CustomEvent("volumedown+sleep", { - bubbles: true, - composed: true, - }) - ); - this.close(); - break; - case "video": - let takeVideo = new WebActivity("record", { - type: "videos" - }); - takeVideo.start(); - this.close(); - break; - case "camera": - let takePhotos = new WebActivity("record", { - type: "photos" - }); - takePhotos.start(); - this.close(); - break; - case "shut-down": - // powerManager.powerOff(); - break; - } - } - - handleContainerEvent(event: Event) { - switch (event.type) { - case "icon-item-click": - this.handleIconButtonClick(event); - break; - case "touch-start": - this.handleIconButtonPress(event); - break; - case "touchend": - this.handleIconButtonPressEnd(event); - break; - case "drag-end": - case "drag-rearrange": - case "drag-finish": - this.handleContainerDrag(event); - break; - } - } - - handleContainerDrag(e: any) { - switch(e.type) { - // Activate drag-and-drop immediately for selected icons - case "touchstart": - this.icons.dragAndDropTimeout = 0; - break; - - case "drag-finish": - debug("Drag-finish"); - // Restore normal drag-and-drop after dragging selected icons - this.icons.dragAndDropTimeout = -1; - break; - - // Handle icon editing and dragging to the end of the icon grid. - case "drag-end": - debug("Drag-end, target: " + (e.detail.dropTarget ? - e.detail.dropTarget.firstElementChild : "none"));//e.detail.target --> div.icon-container - - if (e.detail.dropTarget === null && - e.detail.clientX >= this.iconsLeft && - e.detail.clientX < this.iconsRight) { - // If the drop target is null, and the client coordinates are - // within the panel, we must be dropping over the start or end of - // the container. - e.preventDefault(); - let bottom = e.detail.clientY < this.lastWindowHeight / 2; - debug("Reordering dragged icon to " + - (bottom ? "bottom" : "top")); - this.icons.reorderChild(e.detail.target, - bottom ? this.icons.firstChild : null as any, - this.storeIconOrder.bind(this)); - break; - } - break; - - // Save the icon grid after rearrangement - case "drag-rearrange": - debug("Drag rearrange"); - this.storeIconOrder(); - break; - } - } - - scan() { - // WifiNetworkList(this.wifiListDialog).scan(); - } - - clearList(enabled: boolean) { - console.log(enabled); - - // WifiNetworkList(this.wifiListDialog).clear(enabled); - } - - activeOrInactive(isActive: boolean, element: HTMLElement) { - if (element.id === "sound") { - element.classList.add("active"); - return; - } - isActive ? element.classList.remove("active") : - element.classList.add("active"); - } - - addIcon() { - this.iconOnlyElements.forEach((icon) => { - let iconDiv = document.createElement("icon-item"); - iconDiv.type = "base"; - iconDiv.id = icon; - let key = null; - switch (icon) { - case "data": - iconDiv.icon = "data"; - iconDiv.bgchange = true; - break; - case "airplane-mode": - iconDiv.icon = "airplane"; - iconDiv.bgchange = true; - break; - case "geolocation": - key = GEOLOCATIONENABLED; - iconDiv.icon = "location"; - iconDiv.bgchange = true; - break; - case "wifi-hotspot": - key = WIFI_HOTSPOT; - iconDiv.icon = "tethering"; - iconDiv.bgchange = true; - break; - case "dark-mode": - iconDiv.icon = "themes"; - iconDiv.bgchange = true; - break; - case "auto-rotate": - key = LOCK_SCREEN_KEY; - iconDiv.icon = "sync"; - iconDiv.bgchange = true; - break; - case "sound": - iconDiv.icon = "alarm"; - iconDiv.id = "sound"; - iconDiv.bgchange = true; - break; - // 填充 无意义 - case "addons": - iconDiv.icon = "addons"; - iconDiv.id = "addons"; - iconDiv.bgchange = true; - break; - } - - if (key) { - iconDiv.settingsKey = key; - } - - // iconDiv.id = icon; - // iconDiv.setAttribute("id", icon); - (this as any)[this.toCamelCase(iconDiv.id)] = iconDiv; - this.addIconContainer(iconDiv); - }); - } - - addIconContainer(iconDiv: IconItem) { - let container = document.createElement("div") as HTMLElement; - container.classList.add("icon-container"); - (container as any).order = -1; - container.appendChild(iconDiv); - - if (!container.parentNode) { - this.icons.appendChild(container); - this.refreshGridSize(); - } - } - - refreshGridSize() { - let children = this.icons.children; - let visibleChildren = 0; - let cols = 4; - let firstVisibleChild = -1; - for (let i = 0, iLen = children.length; i < iLen; i++) { - // if ((children[i] as HTMLElement).style.display !== "none") { - visibleChildren ++; - if (firstVisibleChild === -1) { - firstVisibleChild = i; - this.iconsLeft = this.icons.getChildOffsetRect((children[i]) as HTMLElement).left; - } else if (visibleChildren === cols) { - this.iconsRight = this.icons.getChildOffsetRect(children[i] as HTMLElement).right; - } - // } - } - } - - toCamelCase(str: string) { - return str.replace(/\-(.)/g, function replacer(str, p1) { - console.log(str); - return p1.toUpperCase(); - }); - } - - storeIconOrder() { - let storedOrders = []; - let children = this.icons.children; - for (let i = 0, iLen = children.length; i < iLen; i++) { - let icon = children[i].firstElementChild as Element; - storedOrders.push({ id: icon.id, order: i }); - } - this.icondb.set(storedOrders).then( - () => {}, - (e) => { - console.error("Error storing icon order", e); - }); - } - - handleStart(event: TouchEvent) { - this.touchAction.start.pageX = event.touches[0].pageX; - this.touchAction.start.pageY = event.touches[0].pageY; - } - - handleMove(event: TouchEvent) { - this.touchAction.last.pageX = event.touches[0].pageX; - this.touchAction.last.pageY = event.touches[0].pageY; - let distanceY = this.touchAction.last.pageY - this.touchAction.start.pageY; - - if (this.touchAction.start.pageY > 0 && - this.touchAction.start.pageY < this.minRange && - this.touchAction.start.pageX > window.screen.width / 2 - && distanceY >= this.minRange) { - this.open(); - } - - if (this.touchAction.start.pageY > window.screen.height - this.minRange - && this.touchAction.start.pageY <= window.screen.height) { - this.close(); - } - } - - open() { - let isOpen = this.getAttribute("open"); - if (isOpen !== "") { - this.setAttribute("open", ""); - } - } - - close() { - let isOpen = this.getAttribute("open"); - if (isOpen == "") { - this.removeAttribute("open"); - } - } - - fireEvent(name: string, value: string | boolean | number) { - this.dispatchEvent( - new CustomEvent("control-center-dispatched", { - detail: { - name, - value - }, - bubbles: true, - composed: true, - }) - ) - } -} - -declare global { - interface HTMLElementTagNameMap { - 'control-center-bar': ControlCenterBar - } -} \ No newline at end of file diff --git a/src/components/control-center/gaia-container-child.js b/src/components/control-center/gaia-container-child.js deleted file mode 100644 index 2e94f9e..0000000 --- a/src/components/control-center/gaia-container-child.js +++ /dev/null @@ -1,103 +0,0 @@ - -export default class GaiaContainerChild { - constructor(element) { - this._element = element; - this.markDirty(); - } - - get element() { - return this._element; - } - - /** - * The element that will contain the child element and control its position. - */ - get container() { - if (!this._container) { - // Create container - let container = document.createElement("div"); - container.classList.add("gaia-container-child"); - container.style.position = "absolute"; - container.style.top = "0"; - container.style.left = "0"; - container.appendChild(this.element);//this.element是div.icon-container - - this._container = container; - } - return this._container; - } - - /** - * The element that will be added to the container that will - * control the element's transform. - */ - get master() { - if (!this._master) { - // Create master - let master = document.createElement("div"); - master.style.visibility = "hidden"; - this._master = master; - - } - return this._master; - } - - /** - * Clears any cached style properties. To be used if elements are - * manipulated outside of the methods of this object. - */ - markDirty() { - this._lastElementWidth = null; - this._lastElementHeight = null; - this._lastElementDisplay = null; - this._lastElementOrder = null; - this._lastMasterTop = null; - this._lastMasterLeft = null; - } - - /** - * Synchronise the size of the master with the managed child element. - */ - synchroniseMaster() { - let master = this.master; - let element = this.element; - - let style = window.getComputedStyle(element); - let display = style.display; - let order = style.order; - let width = element.offsetWidth; - let height = element.offsetHeight; - - if (this._lastElementWidth !== width || - this._lastElementHeight !== height || - this._lastElementDisplay !== display || - this._lastElementOrder !== order) { - this._lastElementWidth = width; - this._lastElementHeight = height; - this._lastElementDisplay = display; - this._lastElementOrder = order; - - master.style.width = width + "px"; - master.style.height = height + "px"; - master.style.display = display; - master.style.order = order; - } - } - - /** - * Synchronise the container's transform with the position of the master. - */ - synchroniseContainer() { - let master = this.master; - let container = this.container; - let top = master.offsetTop; - let left = master.offsetLeft; - - if (this._lastMasterTop !== top || - this._lastMasterLeft !== left) { - this._lastMasterTop = top; - this._lastMasterLeft = left; - container.style.transform = "translate(" + left + "px, " + top + "px)"; - } - } -} \ No newline at end of file diff --git a/src/components/control-center/gaia-container.js b/src/components/control-center/gaia-container.js deleted file mode 100644 index c1f1623..0000000 --- a/src/components/control-center/gaia-container.js +++ /dev/null @@ -1,789 +0,0 @@ -import GaiaContainerChild from "./gaia-container-child.js"; - -/** - * The time, in ms, to wait for an animation to start in response to a state - * change, before removing the associated style class. - */ -const STATE_CHANGE_TIMEOUT = 100; - -/** - * The time, in ms, to wait before initiating a drag-and-drop from a - * long-press. - */ -const DEFAULT_DND_TIMEOUT = 300; - -/** - * The distance, in CSS pixels, in which a touch or mouse point can deviate - * before it being discarded for initiation of a drag-and-drop action. - */ -const DND_THRESHOLD = 5; - -/** - * The minimum time between sending move events during drag-and-drop. - */ -const DND_MOVE_THROTTLE = 50; - -class GaiaContainer extends HTMLElement { - constructor() { - super(); - this.attachShadow({ mode: "open" }); - this.shadowRoot.innerHTML = this.template; - } - - connectedCallback() { - this._frozen = false; - this._pendingStateChanges = []; - this._children = []; - this._dnd = { - // Whether drag-and-drop is enabled - enabled: false, - - // The time, in ms, to wait before initiating a drag-and-drop from a - // long-press - delay: DEFAULT_DND_TIMEOUT, - - // Timeout used to initiate drag-and-drop actions - timeout: null, - - // The child that was tapped/clicked - child: null, - - // Whether a drag is active - active: false, - - // The start point of the drag action - start: { - pageX: 0, - pageY: 0, - clientX: 0, - clientY: 0 - }, - - // The last point of the drag action - last: { - pageX: 0, - pageY: 0, - clientX: 0, - clientY: 0, - timeStamp: 0 - }, - - // Timeout used to send drag-move events - moveTimeout: null, - - // The last time a move event was fired - lastMoveEventTime: 0, - - // Whether to capture the next click event - clickCapture: false, - }; - - let dndObserverCallback = (mutations) => { - if (this._dnd.enabled !== this.dragAndDrop) { - this._dnd.enabled = this.dragAndDrop; - if (this._dnd.enabled) { - this.addEventListener("touchstart", this); - this.addEventListener("touchmove", this); - this.addEventListener("touchcancel", this); - this.addEventListener("touchend", this); - this.addEventListener("mousedown", this); - this.addEventListener("mousemove", this); - this.addEventListener("mouseup", this); - this.addEventListener("click", this, true); - this.addEventListener("contextmenu", this, true); - } else { - this.cancelDrag(); - this.removeEventListener("touchstart", this); - this.removeEventListener("touchmove", this); - this.removeEventListener("touchcancel", this); - this.removeEventListener("touchend", this); - this.removeEventListener("mousedown", this); - this.removeEventListener("mousemove", this); - this.removeEventListener("mouseup", this); - this.removeEventListener("click", this, true); - this.removeEventListener("contextmenu", this, true); - } - } - }; - this.dndObserver = new MutationObserver(dndObserverCallback); - this.dndObserver.observe(this, { - attributes: true, - attributeFilter: ["drag-and-drop"] - }); - - dndObserverCallback(null); - } - - get children() { //div.icon-container - return this._children.map((child) => { - return child.element; - }); - } - - get firstChild() { - return this._children.length ? - this._children[0].element : - null; - } - - get lastChild() { - const length = this._children.length; - return (length ? - this._children[length - 1].element : - null); - } - - get dragAndDrop() { - return (this.getAttribute("drag-and-drop") !== null); - } - - get dragAndDropTimeout() { - return this._dnd.delay; - } - - set dragAndDropTimeout(timeout) { - if (timeout >= 0) { - this._dnd.delay = timeout; - } else { - this._dnd.delay = DEFAULT_DND_TIMEOUT; - } - } - - realAppendChild() { - let proto = HTMLElement.prototype.appendChild; - let func = proto.apply(this, arguments); - return func; - } - - realRemoveChild() { - let proto = HTMLElement.prototype.removeChild; - let func = proto.apply(this, arguments); - return func; - } - - realInsertBefore() { - let proto = HTMLElement.prototype.insertBefore; - let func = proto.apply(this, arguments); - return func; - } - - realReplaceChild() { - let proto = HTMLElement.prototype.replaceChild; - let func = proto.apply(this, arguments); - return func; - } - - appendChild(element, callback) { - this.insertBefore(element, null, callback); - } - - removeChild(element, callback) { - let children = this._children; - let childToRemove = null; - - for (let child of children) { - if (child.element === element) { - childToRemove = child; - break; - } - } - - if (childToRemove === null) { - throw "removeChild called on unknown child"; - } - let that = this; - this.changeState(childToRemove, "removed", () => { - that.realRemoveChild(childToRemove.container); - that.realRemoveChild(childToRemove.master); - - // Find the child again. We need to do this in case the container was - // manipulated between removing the child and this callback being reached. - for (let i = 0, iLen = children.length; i < iLen; i++) { - if (children[i] === childToRemove) { - children.splice(i, 1); - break; - } - } - - if (callback) { - callback(); - } - - this.synchronise(); - }); - } - - replaceChild(newElement, oldElement, callback) { - if (!newElement || !oldElement) { - throw "replaceChild called with null arguments"; - } - - // Unparent the newElement if necessary (with support for gaia-container) - if (newElement.parentNode) { - newElement.parentNode.removeChild(newElement, () => { - this.replaceChild(newElement, oldElement, callback); - }); - if (newElement.parentNode) { - return; - } - } - - // Remove the old child and add the new one, but don't run the removed/ - // added state changes. - let children = this._children; - - for (let i = 0, iLen = children.length; i < iLen; i++) { - let oldChild = children[i]; - if (oldChild.element === oldElement) { - let newChild = new GaiaContainerChild(newElement); - this.realInsertBefore(newChild.container, oldChild.container); - this.realInsertBefore(newChild.master, oldChild.master); - this.realRemoveChild(oldChild.container); - this.realRemoveChild(oldChild.master); - this.children.splice(i, 1, newChild); - this.synchronise(); - - if (callback) { - callback(); - } - return; - } - } - - throw "removeChild called on unknown child"; - } - - /** - * Reorders the given element to appear before referenceElement. - */ - reorderChild(element, referenceElement, callback) { - if (!element) { - throw "reorderChild called with null element"; - } - - let children = this._children; - let child = null; - let childIndex = null; - let referenceChild = null; - let referenceChildIndex = null; - - for (let i = 0, iLen = children.length; i < iLen; i++) { - if (children[i].element === element) { - child = children[i]; - childIndex = i; - } else if (children[i].element === referenceElement) { - referenceChild = children[i]; - referenceChildIndex = i; - } - - if (child && (referenceChild || !referenceElement)) { - this.realRemoveChild(child.container); - this.realRemoveChild(child.master); - children.splice(childIndex, 1); - - if (referenceChild) { - this.realInsertBefore(child.container, referenceChild.container); - this.realInsertBefore(child.master, referenceChild.master); - } else { - (children.length === 0) ? - this.realAppendChild(child.container): - this.realInsertBefore(child.container, children[0].master); - this.realAppendChild(child.master); - } - - referenceChild ? - children.splice( - referenceChildIndex - (childIndex < referenceChildIndex) ? 1 : 0, - 0, child) : - children.splice(children.length, 0, child); - - this.synchronise(); - - if (callback) { - callback(); - } - return; - } - } - - throw child ? "reorderChild called on unknown reference element" : - "reorderChild called on unknown child"; - } - - insertBefore(element, reference, callback) { - let children = this._children; - let childToInsert = new GaiaContainerChild(element); - // console.log("childToInsert: ",childToInsert);// gaia-container-child - // console.log("children: ", children);//包括gaia-container-child 、icon-container等 - let referenceIndex = -1; - - if (reference !== null) { - for (let i = 0, iLen = children.length; i < iLen; i++) { - if (children[i].element === reference) { - referenceIndex = i; - break; - } - } - if (referenceIndex === -1) { - throw "insertBefore called on unknown child"; - } - } - - if (referenceIndex === -1) { - (children.length === 0) ? - this.realAppendChild(childToInsert.container): - this.realInsertBefore(childToInsert.container, children[0].master); - this.realAppendChild(childToInsert.master); - children.push(childToInsert); - } else { - this.realInsertBefore(childToInsert.container, - children[referenceIndex].container); - this.realInsertBefore(childToInsert.master, - children[referenceIndex].master); - children.splice(referenceIndex, 0, childToInsert); - } - - this.changeState(childToInsert, "added", callback); - this.synchronise(); - } - - /** - * Used to execute a state-change of a child that may possibly be animated. - * @state will be added to the child's class-list. If an animation starts that - * has the same name, that animation will complete and @callback will be - * called. Otherwise, the class will be removed and @callback called on the - * next frame. - */ - changeState(child, state, callback) { - // Check that the child is still attached to this parent (can happen if - // the child is removed while frozen). - if (child.container.parentNode !== this) { - return; - } - - // Check for a redundant state change. - if (child.container.classList.contains(state)) { - return; - } - - // Queue up state change if we're frozen. - if (this._frozen) { - this._pendingStateChanges.push( - this.changeState.bind(this, child, state, callback)); - return; - } - - let animStart = (e) => { - if (!e.animationName.endsWith(state)) { - return; - } - - child.container.removeEventListener("animationstart", animStart); - - window.clearTimeout(child[state]); - delete child[state]; - - let self = this; - child.container.addEventListener("animationend", function animEnd() { - child.container.removeEventListener("animationend", animEnd); - child.container.classList.remove(state); - if (callback) { - callback(); - } - }); - }; - - child.container.addEventListener("animationstart", animStart); - child.container.classList.add(state); - - child[state] = window.setTimeout(() => { - delete child[state]; - child.container.removeEventListener("animationstart", animStart); - child.container.classList.remove(state); - if (callback) { - callback(); - } - }, STATE_CHANGE_TIMEOUT); - } - - getChildOffsetRect(element) { - let children = this._children; - for (let i = 0, iLen = children.length; i < iLen; i++) { - let child = children[i]; - if (child.element === element) { - let top = child._lastMasterTop; - let left = child._lastMasterLeft; - let width = child._lastElementWidth; - let height = child._lastElementHeight; - - return { - top: top, - left: left, - width: width, - height: height, - right: left + width, - bottom: top + height - }; - } - } - - throw "getChildOffsetRect called on unknown child"; - } - - getChildFromPoint(x, y) { - let children = this._children; - for (let parent = this.parentElement; parent; parent = parent.parentElement) { - x += parent.scrollLeft - parent.offsetLeft; - y += parent.scrollTop - parent.offsetTop; - } - for (let i = 0, iLen = children.length; i < iLen; i++) { - let child = children[i]; - if (x >= child._lastMasterLeft && - y >= child._lastMasterTop && - x < child._lastMasterLeft + child._lastElementWidth && - y < child._lastMasterTop + child._lastElementHeight) { - return child.element; - } - } - - return null; - } - - cancelDrag() { - if (this._dnd.timeout !== null) { - clearTimeout(this._dnd.timeout); - this._dnd.timeout = null; - } - - if (this._dnd.moveTimeout !== null) { - clearTimeout(this._dnd.moveTimeout); - this._dnd.moveTimeout = null; - } - - if (this._dnd.active) { - this._dnd.child.container.classList.remove("dragging"); - this._dnd.child.container.style.position = "absolute"; - this._dnd.child.container.style.top = "0"; - this._dnd.child.container.style.left = "0"; - this._dnd.child.markDirty(); - this._dnd.child = null; - this._dnd.active = false; - this.synchronise(); - this._dnd.clickCapture = true; - this.dispatchEvent(new CustomEvent("drag-finish")); - } - } - - startDrag() { - if (!this.dispatchEvent(new CustomEvent("drag-start", { - cancelable: true, - detail: { - target: this._dnd.child.element, - pageX: this._dnd.start.pageX, - pageY: this._dnd.start.pageY, - clientX: this._dnd.start.clientX, - clientY: this._dnd.start.clientY - } - }))) { - return; - } - - this._dnd.active = true; - this._dnd.child.container.classList.add("dragging"); - this._dnd.child.container.style.position = "fixed"; - let rect = this.getBoundingClientRect(); - this._dnd.child.container.style.top = rect.top + "px"; - this._dnd.child.container.style.left = rect.left + "px"; - } - - continueDrag() { - if (!this._dnd.active) { - return; - } - - let left = this._dnd.child.master.offsetLeft + - (this._dnd.last.pageX - this._dnd.start.pageX); - let top = this._dnd.child.master.offsetTop + - (this._dnd.last.pageY - this._dnd.start.pageY); - this._dnd.child.container.style.transform = - "translate(" + left + "px, " + top + "px)"; - - if (this._dnd.moveTimeout === null) { - let delay = Math.max(0, DND_MOVE_THROTTLE - - (this._dnd.last.timeStamp - this._dnd.lastMoveEventTime)); - this._dnd.moveTimeout = setTimeout(() => { - this._dnd.moveTimeout = null; - this._dnd.lastMoveEventTime = this._dnd.last.timeStamp; - this.dispatchEvent(new CustomEvent("drag-move", { - detail: { - target: this._dnd.child.element, - pageX: this._dnd.last.pageX, - pageY: this._dnd.last.pageY, - clientX: this._dnd.last.clientX, - clientY: this._dnd.last.clientY - } - })); - }, delay); - } - } - - endDrag(event) { - if (this._dnd.active) { - let dropTarget = this.getChildFromPoint(this._dnd.last.clientX, - this._dnd.last.clientY); - - if (this.dispatchEvent(new CustomEvent("drag-end", { - cancelable: true, - detail: { - target: this._dnd.child.element, - dropTarget: dropTarget, - pageX: this._dnd.last.pageX, - pageY: this._dnd.last.pageY, - clientX: this._dnd.last.clientX, - clientY: this._dnd.last.clientY - } - }))) { - let children = this._children; - - if (dropTarget && dropTarget !== this._dnd.child.element) { - let dropChild = null; - let dropIndex = -1; - let childIndex = -1; - let insertBefore = true; - for (let i = 0, iLen = children.length; i < iLen; i++) { - if (children[i] === this._dnd.child) { - childIndex = i; - if (!dropChild) { - insertBefore = false; - } - } - - if (children[i].element === dropTarget) { - dropChild = children[i]; - dropIndex = i; - } - - if (dropIndex >= 0 && childIndex >= 0) { - break; - } - } - - if (dropIndex >= 0 && childIndex >= 0) { - // Default action, rearrange the dragged child to before or after - // the child underneath the touch/mouse point. - this.realRemoveChild(this._dnd.child.container); - this.realRemoveChild(this._dnd.child.master); - this.realInsertBefore(this._dnd.child.container, - insertBefore ? dropChild.container : - dropChild.container.nextSibling); - this.realInsertBefore(this._dnd.child.master, - insertBefore ? dropChild.master : - dropChild.master.nextSibling); - children.splice(dropIndex, 0, - children.splice(childIndex, 1)[0]); - this.dispatchEvent(new CustomEvent("drag-rearrange")); - } - } - } - } else if (this._dnd.timeout !== null) { - let handled = !this.dispatchEvent(new CustomEvent("activate", { - cancelable: true, - detail: { - target: this._dnd.child.element - } - })); - if (handled) { - event.stopImmediatePropagation(); - event.preventDefault(); - } - } - - this.cancelDrag(); - } - - handleEvent(event) { - switch (event.type) { - case "touchstart": - case "mousedown": - if (this._dnd.active || this._dnd.timeout) { - this.cancelDrag(); - break; - } - - if (event instanceof MouseEvent) { - this._dnd.start.pageX = event.pageX; - this._dnd.start.pageY = event.pageY; - this._dnd.start.clientX = event.clientX; - this._dnd.start.clientY = event.clientY; - // this._dnd.start.clientX = event.pageX; - // this._dnd.start.clientY = event.pageY; - } else { - this._dnd.start.pageX = event.touches[0].pageX; - this._dnd.start.pageY = event.touches[0].pageY; - this._dnd.start.clientX = event.touches[0].clientX; - this._dnd.start.clientY = event.touches[0].clientY; - } - - this._dnd.last.pageX = this._dnd.start.pageX; - this._dnd.last.pageY = this._dnd.start.pageY; - this._dnd.last.clientX = this._dnd.start.clientX; - this._dnd.last.clientY = this._dnd.start.clientY; - this._dnd.last.timeStamp = event.timeStamp; - - let target = event.target;//gaia-app-icon - - for (; target.parentNode !== this; target = target.parentNode) { - if (target === this || !target.parentNode) { - return; - } - } - - // Find the child - let children = this._children; - for (let child of children) { - if (child.container === target) { - this._dnd.child = child; - break; - } - } - - if (!this._dnd.child) { - return; - } - - if (this._dnd.delay > 0) { - this._dnd.timeout = setTimeout(() => { - this._dnd.timeout = null; - this.startDrag(); - }, this._dnd.delay); - } else { - this.startDrag(); - } - break; - - case "touchmove": - case "mousemove": - let pageX, pageY, clientX, clientY; - if (event instanceof MouseEvent) { - pageX = event.pageX; - pageY = event.pageY; - clientX = event.clientX; - clientY = event.clientY; - } else { - pageX = event.touches[0].pageX; - pageY = event.touches[0].pageY; - clientX = event.touches[0].clientX; - clientY = event.touches[0].clientY; - } - - if (this._dnd.timeout) { - if (Math.abs(pageX - this._dnd.start.pageX) > DND_THRESHOLD || - Math.abs(pageY - this._dnd.start.pageY) > DND_THRESHOLD) { - clearTimeout(this._dnd.timeout); - this._dnd.timeout = null; - } - } else if (this._dnd.active) { - event.preventDefault(); - this._dnd.last.pageX = pageX; - this._dnd.last.pageY = pageY; - this._dnd.last.clientX = clientX; - this._dnd.last.clientY = clientY; - this._dnd.last.timeStamp = event.timeStamp; - - this.continueDrag(); - } - break; - - case "touchcancel": - this.cancelDrag(); - break; - - case "touchend": - case "mouseup": - if (this._dnd.active) { - event.preventDefault(); - event.stopImmediatePropagation(); - } - this.endDrag(event); - break; - - case "click": - if (this._dnd.clickCapture) { - this._dnd.clickCapture = false; - event.preventDefault(); - event.stopImmediatePropagation(); - } - break; - - case "contextmenu": - if (this._dnd.active || this._dnd.timeout) { - event.stopImmediatePropagation(); - // event.preventDefault(); - } - break; - } - } - - /** - * Temporarily disables element position synchronisation. Useful when adding - * multiple elements to the container at the same time, or in quick - * succession. - */ - freeze() { - this._frozen = true; - } - - /** - * Enables element position synchronisation after freeze() has been called. - */ - thaw() { - if (this._frozen) { - this._frozen = false; - for (let callback of this._pendingStateChanges) { - callback(); - } - this._pendingStateChanges = []; - this.synchronise(); - } - } - - /** - * Synchronise positions between the managed container and all children. - * This is called automatically when adding/inserting or removing children, - * but must be called manually if the managed container is manipulated - * outside of these methods (for example, if style properties change, or - * if it's resized). - */ - synchronise() { - if (this._frozen) { - return; - } - - let child; - for (child of this._children) { - if (!this._dnd.active || child !== this._dnd.child) { - child.synchroniseMaster(); - } - } - - for (child of this._children) { - if (!this._dnd.active || child !== this._dnd.child) { - child.synchroniseContainer(); - } - } - } - - shadowStyle = ` - :host { - position: relative; - display: block; - } - ` - - template = ` - - - ` -} - -customElements.define("gaia-container", GaiaContainer); diff --git a/src/components/control-center/header-bar.ts b/src/components/control-center/header-bar.ts deleted file mode 100644 index fd9be7f..0000000 --- a/src/components/control-center/header-bar.ts +++ /dev/null @@ -1,289 +0,0 @@ -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` -
-
-
- -
-
- `; - } - - getTimeAndDate(): HTMLTemplateResult { - return html` -
-
-
-
-
-
- -
-
- `; - } - - 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 { - height: 52px; - line-height: 52px; - font-family: OPPOSans; - font-style: normal; - font-weight: 400; - font-size: 52px; - color: var(--only-time-color-lm); - } - - .time-icons { - display: flex; - width: 136px; - position: relative; - right: 4px; - } - - .time-icons > ::slotted(*) { - display: flex; - } - - .time-icons > ::slotted(:last-child) { - position: relative; - left: 40px; - } - - .time-date-outer { - width: 100%; - height: 100%; - display: flex; - align-items: end; - } - - .time-date { - height: 100%; - width: 100%; - display: flex; - align-items: end; - line-height: 64px; - vertical-align: middle; - font-family: 'OPPOSans'; - font-style: normal; - font-weight: 400; - position: relative; - left: 10px; - } - - .time-date > #time { - color: var(--time-date-time-color-lm); - font-size: 64px; - } - - #date { - height: 34px; - line-height: 34px; - position: relative; - left: 22px; - font-size: 26px; - opacity: 0.6; - color: var(--time-date-date-color-lm); - mix-blend-mode: normal; - } - - .time-date-icons { - position: relative; - right: 11px; - } - - .host([deep-mode]) .time-outer > #time { - height: 52px; - line-height: 52px; - font-family: OPPOSans; - font-style: normal; - font-weight: 400; - font-size: 52px; - color: var(--only-time-color-dm); - } - - .host([deep-mode]) .time-date > #time { - color: var(--time-date-time-color-dm); - font-size: 64px; - } - - .host([deep-mode]) #date { - height: 34px; - line-height: 34px; - position: relative; - left: 22px; - font-size: 26px; - opacity: 0.6; - color: var(--time-date-date-color-dm); - mix-blend-mode: normal; - } - ` - - 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 - } -} \ No newline at end of file diff --git a/src/components/control-center/icon-item.ts b/src/components/control-center/icon-item.ts deleted file mode 100644 index a3bdedd..0000000 --- a/src/components/control-center/icon-item.ts +++ /dev/null @@ -1,318 +0,0 @@ -import { html, css, LitElement, HTMLTemplateResult, nothing } from 'lit' -import { customElement, property, eventOptions, query, state } from 'lit/decorators.js' - -export enum IconType { - BASE = 'base', - BASE_WITHOUT_BORDER = 'base-without-border', - WITH_STATE = 'with-state' -} - -@customElement('icon-item') -export class IconItem extends LitElement { - @property({type: IconType}) 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; - @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-item】缺少 icon 参数'); - return nothing; - } - - const iconstyle = html` - - `; - - return html ` -
- ${iconstyle} -
- `; - } - - getbaseWithOutBorder(): HTMLTemplateResult | typeof nothing { - if (!this.icon) { - console.error('【icon-item】缺少 icon 参数'); - return nothing; - } - - return html ` -
-
- `; - } - - getwithstate(): HTMLTemplateResult | typeof nothing { - if (!this.icon) { - console.error('【icon-item】缺少 icon 参数'); - return nothing; - } - - const iconstyle = html` - - `; - - return html` -
-

${this.stateDesc}

-
- ${iconstyle} -
- `; - } - - @eventOptions({passive: false}) - handleClick(event: Event) { - 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 == IconType.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 = ""; - // } - // } - - let self = this; - window.dispatchEvent(new CustomEvent("icon-item-click", { - detail: { - id: self.id ? self.id : self.icon, - isActive: isActive, - target: self - } - })); - } - - handleInfo() { - let self = this; - window.dispatchEvent(new CustomEvent("more-info-action", {detail: self.icon})); - } - - handlePress(event: Event) { - let target = event.target as HTMLElement; - if (target.nodeName == "P") { - target = target.parentElement as HTMLElement; - } - - if (this.getAttribute("disabled") === "true") { - return; - } - - let section = this.icon; - this.timer = setTimeout(() => { - let activity = new WebActivity("moz_configure_window", { - data: { - target: "device", - section: section - } - }); - activity.start(); - }, 300); - - let self = this; - window.dispatchEvent(new CustomEvent("touch-start", { - detail: { - id: self.id ? self.id : self.icon, - target: self - } - })); - } - - handlePressEnd() { - clearTimeout(this.timer); - } - - 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 IconType.BASE: - return this.getbase(); - case IconType.BASE_WITHOUT_BORDER: - return this.getbaseWithOutBorder(); - case IconType.WITH_STATE: - return this.getwithstate(); - default: - console.error('unhandled 【icon-item】 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; - } - - .with-border { - width: 100%; - height: 100%; - display: flex; - align-items: center; - border-radius: 16px; - background: var(--background-lm); - } - - .icon-button::before { - width: 48px; - height: 48px; - line-height: 48px; - text-align: center; - vertical-align: middle; - content: attr(data-icon); - font-size: 48px; - font-family: gaia-icons; - font-style: normal; - text-rendering: optimizelegibility; - font-weight: 500; - } - - .icon-with-state::before { - position: relative; - left: 22px; - } - - .icon-base { - justify-content: center; - } - - .more-info-icon { - width: 16px; - height: 16px; - position: relative; - left: 58px; - } - - .more-info-icon::after { - width: 16px; - height: 16px; - text-align: center; - vertical-align: middle; - line-height: 16px; - content: attr(data-icon); - font-size: 16px; - font-family: gaia-icons; - font-style: normal; - text-rendering: optimizelegibility; - font-weight: 500; - } - - p { - width: 100px; - height: 20px; - position: relative; - left: 34px; - color: var(--text-color-lm); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - font-family: OPPOSans; - font-style: normal; - font-weight: 400; - font-size: 21px; - line-height: 20px; - mix-blend-mode: normal; - } - - .active > p { - color: var(--text-color-active); - } - - :host([deep-mode]) .with-border { - width: 100%; - height: 100%; - display: flex; - align-items: center; - border-radius: 16px; - // border-radius: 8px; - background: var(--background-dm); - } - - :host([deep-mode]) p { - width: 100px; - height: 20px; - position: relative; - left: 34px; - color: var(--text-color-dm); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - font-family: OPPOSans; - font-style: normal; - font-weight: 400; - font-size: 21px; - line-height: 20px; - mix-blend-mode: normal; - } - ` -} - -declare global { - interface HTMLElementTagNameMap { - 'icon-item': IconItem - } -} \ No newline at end of file diff --git a/src/components/control-center/icondb.ts b/src/components/control-center/icondb.ts deleted file mode 100644 index 580f7b0..0000000 --- a/src/components/control-center/icondb.ts +++ /dev/null @@ -1,88 +0,0 @@ -export default class IconDB { - db: any; - DB_NAME = 'icon-db'; - DB_ORDER_STORE = 'order'; - DB_VERSION = 1; - - init() { - return new Promise((resolve: (value: void) => void, reject) => { - var req = window.indexedDB.open(this.DB_NAME, this.DB_VERSION); - req.onupgradeneeded = this.upgradeSchema; - req.onsuccess = () => { - this.db = req.result; - resolve(); - }; - req.onerror = (e) => { - console.error('Error opening icon db', e); - reject(e); - }; - }); - } - - upgradeSchema(e: any) { - var db = e.target.result; - var fromVersion = e.oldVersion; - if (fromVersion < 1) { - var store = db.createObjectStore(this.DB_ORDER_STORE, { keyPath: 'id' }); - store.createIndex('order', 'order', { unique: false }); - } - } - - set(data: any) { - return new Promise((resolve, reject) => { - var txn = this.db.transaction(this.DB_ORDER_STORE, - 'readwrite'); - for (var entry of data) { - if (!entry.id) { - continue; - } - - if (typeof entry.order !== 'undefined') { - txn.objectStore(this.DB_ORDER_STORE). - put({ id: entry.id, order: entry.order }); - } - } - txn.oncomplete = resolve; - txn.onerror = reject; - }); - } - - remove(id: string) { - return new Promise((resolve, reject) => { - var txn = this.db.transaction(this.DB_ORDER_STORE, 'readwrite'); - txn.objectStore(this.DB_ORDER_STORE).delete(id); - txn.oncomplete = resolve; - txn.onerror = reject; - }); - } - - getAll(onResult: Function) { - return new Promise((resolve: (value?: any) => void) => { - try { - - var txn = this.db.transaction(this.DB_ORDER_STORE, 'readonly'); - var orderStore = txn.objectStore(this.DB_ORDER_STORE); - var cursor = orderStore.index('order').openCursor(); - var results: any[] = []; - - cursor.onsuccess = (e: any) => { - var cursor = e.target.result; - if (cursor) { - var result = cursor.value; - console.log("ffff result: ", result); - if (onResult) { - onResult(result); - } - cursor.continue(); - } - }; - - txn.oncomplete = () => { - resolve(results); - }; - } catch (error) { - resolve("error"); - } - }); - } -} \ No newline at end of file diff --git a/src/components/control-center/icons-one-line.ts b/src/components/control-center/icons-one-line.ts deleted file mode 100644 index b6a8fa1..0000000 --- a/src/components/control-center/icons-one-line.ts +++ /dev/null @@ -1,137 +0,0 @@ -import { html, css, LitElement, HTMLTemplateResult, nothing } from 'lit' -import { customElement, property, queryAssignedElements } from 'lit/decorators.js' - -export enum IconsOneLineType { - BASE = 'base', - WITH_STATE = 'with-state' -} - -@customElement('icons-one-line') -export class IconsOneLine extends LitElement { - @property({type: IconsOneLineType}) type = ''; - @property({type: Number}) count = 2; - @queryAssignedElements({flatten: true}) slotElements!: HTMLSlotElement[]; - - getbase(): HTMLTemplateResult { - const colorstyle = this.count !== 4 - ? html` - - ` - : nothing; - return html` -
-
- -
- ${colorstyle} -
- `; - } - - getIconWithState(): HTMLTemplateResult { - return html` -
- -
- `; - } - - firstUpdated() { - this.count = this.slotElements.length; - } - - render() { - switch (this.type) { - case IconsOneLineType.BASE: - return this.getbase(); - case IconsOneLineType.WITH_STATE: - return this.getIconWithState(); - default: - console.error('unhandled 【icons-one-line】 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); - border-radius: 16px; - } - - .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; - border-left: 2px solid var(--line-border-lm); - border-radius: 2px; - } - - .icon-only > div > ::slotted(:first-child) { - border-left-style: none; - } - - :host([deep-mode]) { - width: inherit; - height: inherit; - background: var(--background-dm); - mix-blend-mode: normal; - opacity: var(--opacity-dm); - border-radius: 16px; - } - - :host([deep-mode]) .icon-only > div > ::slotted(*) { - width: 25%; - height: 30px; - display: flex; - align-items: center; - justify-content: center; - border-left: 2px solid var(--line-border-dm); - border-radius: 2px; - } - ` -} - -declare global { - interface HTMLElementTagNameMap { - 'icons-one-line': IconsOneLine - } -} \ No newline at end of file diff --git a/src/components/control-center/index.ts b/src/components/control-center/index.ts deleted file mode 100644 index baeddc1..0000000 --- a/src/components/control-center/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './control-center-bar.js' \ No newline at end of file diff --git a/src/components/control-center/light-slider.ts b/src/components/control-center/light-slider.ts deleted file mode 100644 index 84284d6..0000000 --- a/src/components/control-center/light-slider.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { html, css, LitElement, HTMLTemplateResult } from 'lit' -import { customElement, property, query } from 'lit/decorators.js' -@customElement('light-slider') -export class LightSlider 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` -
-
-
- ` - } - - 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); - border-radius: 16px; - } - - .progress { - width: var(--cover-length); - height: 100%; - position: absolute; - left: 0px; - right: 0px; - background: var(--progress-background-lm); - border-radius: 16px; - } - - .sliderBar::before { - position: absolute; - width: 48px; - height: 48px; - left: 29px; - top: 29px; - content: attr(data-icon); - font-size: 48px; - font-family: gaia-icons; - font-style: normal; - text-rendering: optimizelegibility; - font-weight: 500; - } - ` - - 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("light-slider-change", { - detail: { - value: this.value - }, - bubbles: true, - composed: true, - })); - break; - } - } - - setValue(value: number) { - console.log("light00 set value: ", value); - this.value = value; - console.log("light00 set this.value: ", this.value); - let len = Math.floor(value * this.sliderBar.offsetWidth / this.max); - this.coverLen = len.toString(); - console.log("light00 set this.coverLen: ", this.coverLen); - } -} - -declare global { - interface HTMLElementTagNameMap { - 'light-slider': LightSlider - } -} \ No newline at end of file diff --git a/src/components/control-center/mock_settings_observer.ts b/src/components/control-center/mock_settings_observer.ts deleted file mode 100644 index 5b35f23..0000000 --- a/src/components/control-center/mock_settings_observer.ts +++ /dev/null @@ -1,88 +0,0 @@ -interface Obj { - name?: string; - value?: any; - callback: (name: string, value: any)=> void; -} - -interface Pair { - name: string; - value: any; -} - - -export var settingsObserver = { - _keyvaluepairs: [] as Pair[], // 存放设定的设置键值对 - _observers: [] as Obj[], // 存放observe方法存入的 - - getValue: function(name: string) { - // return new Promise((resolve: (value: any) => void) => { - // resolve(true); - // }) - - return new Promise(resovle => { - let value; - this._keyvaluepairs.forEach((pair: any) => { - if(pair.name == name) { - value = pair.value; - } - }); - return resovle(value); - }) - }, - - observe: function(name: string, defaultValue: any, callback: (name: string, value: any)=> void, observeOnly: boolean) { - if (!observeOnly) { - this.getValue(name).then((value) => { - callback(name, value !== null && value !== void 0 ? value : defaultValue); // 重启前设置值为false或0时,重启后返回的值不应该为默认值 - }); - } - this._observers.push({ - name, - callback - }); - }, - - setValue: function(args: any[]) { - return new Promise((resovle: (value: void)=> void) => { - // 将传入的设置值更新/设置入_keyvaluepairs - args.forEach((pair: any) => { - let targetPair = this._keyvaluepairs.find((item: any) => { - return item.name == pair.name - }); - if (targetPair as any) { - // 在_keyvaluepairs根据pair.name找到了相同的设置值,更新键值对的value - (targetPair as any).value = pair.value; - } else { - // 在_keyvaluepairs不存在该设置值,新增键值对 - let newPair = { - name: pair.name, - value: pair.value - }; - this._keyvaluepairs.push(newPair); - } - - // 同时检查_observers中是否有该设置值变化时的回调 - this._observers.forEach(ob => { - ob.name = pair.name && ob.callback?.(pair.name, pair.value); - }); - - resovle(); - }); - }) - }, - - getBatch: function (names: string[]) { - return new Promise((resolve: ({}) => void) => { - let result = []; - for (let index = 0; index < names.length; index++) { - const name = names[index]; - let value = true; - result.push({name, value: value}); - } - - if (result.length == names.length) { - resolve(result); - } - }) - } -} \ No newline at end of file diff --git a/src/components/control-center/package.json b/src/components/control-center/package.json deleted file mode 100644 index 88694f8..0000000 --- a/src/components/control-center/package.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "@star-web-components/control-center-bar", - "version": "0.0.1", - "description": "", - "type": "module", - "main": "./index.js", - "module": "./index.js", - "exports": { - ".": { - "default": "./index.js" - }, - "./index": { - "default": "./index.js" - }, - "./icon-item.js": { - "default": "./icon-item.js" - }, - "./icons-one-line.js": { - "default": "./icons-one-line.js" - }, - "./header-bar.js": { - "default": "./header-bar.js" - }, - "./light-slider.js": { - "default": "./light-slider.js" - }, - "./pull-down-menu.js": { - "default": "./pull-down-menu.js" - }, - "./control-center-bar.js": { - "default": "./control-center-bar.js" - }, - "./package.json": "./package.json" - }, - "author": "", - "license": "ISC" -} diff --git a/src/components/control-center/pull-down-menu.ts b/src/components/control-center/pull-down-menu.ts deleted file mode 100644 index 0007283..0000000 --- a/src/components/control-center/pull-down-menu.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { html, css, LitElement, HTMLTemplateResult, nothing } from 'lit' -import { customElement, property, queryAssignedElements } from 'lit/decorators.js' -import { HeaderBarType } from './header-bar'; -import { IconItem, IconType } from './icon-item'; -import './icon-item'; -import './header-bar'; -export enum PullDownMenuType { - HEADER_WITH_TIME = 'header-with-time', - HEADER_WITH_DATE_TIME = 'header-with-date-time' -} - -@customElement('pull-down-menu') -export class PullDownMenu extends LitElement { - @property({type: PullDownMenuType}) type = ''; - @queryAssignedElements({flatten: true}) slotElements!: HTMLSlotElement[]; - getOnlyTime(): HTMLTemplateResult { - return html` -
- - - - -
- -
-
- ` - } - - getTimeAndDate(): HTMLTemplateResult { - return html` -
- - - -
- -
-
- ` - } - - render(): HTMLTemplateResult | typeof nothing { - switch (this.type) { - case PullDownMenuType.HEADER_WITH_TIME: - return this.getOnlyTime(); - case PullDownMenuType.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; - position: absolute; - left: 0px; - top: 0px; - } - - :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; - } - - 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; - } - ` - - handleEvent(event: Event) { - switch (event.type) { - case "click": - switch ((event.target as IconItem).icon) { - case "compose": - window.dispatchEvent(new CustomEvent("editor-action")); - break; - case "settings": - let activity = new WebActivity("configure", {}); - activity.start().then(() => {}, () => {}); - break; - } - break; - } - - } -} - -declare global { - interface HTMLElementTagNameMap { - 'pull-down-menu': PullDownMenu - } -} \ No newline at end of file diff --git a/src/components/drop-down-menu/drop-down-menu.ts b/src/components/drop-down-menu/drop-down-menu.ts new file mode 100644 index 0000000..ac578a5 --- /dev/null +++ b/src/components/drop-down-menu/drop-down-menu.ts @@ -0,0 +1,173 @@ +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 {IconControlBar, IconControlBarType} from '../icon-control-bar/icon-control-bar.js' +import {WebActivity} from './interface.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('pull-down-menu') +export class DropDownMenu extends LitElement { + @property({type: DropDownMenuType}) type = '' + @queryAssignedElements({flatten: true}) slotElements!: HTMLSlotElement[] + getOnlyTime(): HTMLTemplateResult { + return html` +
+ + + + +
+ +
+
+ ` + } + + getTimeAndDate(): HTMLTemplateResult { + return html` +
+ + + +
+ +
+
+ ` + } + + 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; + position: absolute; + left: 0px; + top: 0px; + } + + :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 (max-width: 1200px) { + 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 (max-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; + } + } + ` + + handleEvent(event: Event) { + switch (event.type) { + case 'click': + switch ((event.target as IconControlBar).icon) { + case 'compose': + window.dispatchEvent(new CustomEvent('editor-action')) + break + case 'settings': + let activity = new WebActivity('configure', {}) + activity.start().then( + () => {}, + () => {} + ) + break + } + break + } + } +} + +declare global { + interface HTMLElementTagNameMap { + 'pull-down-menu': DropDownMenu + } +} diff --git a/src/components/drop-down-menu/index.ts b/src/components/drop-down-menu/index.ts new file mode 100644 index 0000000..cb457f9 --- /dev/null +++ b/src/components/drop-down-menu/index.ts @@ -0,0 +1 @@ +export * from './drop-down-menu.js' diff --git a/src/components/drop-down-menu/interface.ts b/src/components/drop-down-menu/interface.ts new file mode 100644 index 0000000..37700c0 --- /dev/null +++ b/src/components/drop-down-menu/interface.ts @@ -0,0 +1,9 @@ +export interface WebActivity { + start(): Promise + cancel(): void +} + +export var WebActivity: { + prototype: WebActivity + new (name: string, data?: any): WebActivity +} diff --git a/src/components/drop-down-menu/package.json b/src/components/drop-down-menu/package.json new file mode 100644 index 0000000..3e0b385 --- /dev/null +++ b/src/components/drop-down-menu/package.json @@ -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" +} diff --git a/src/components/drop-down-menu/tsconfig.json b/src/components/drop-down-menu/tsconfig.json new file mode 100644 index 0000000..f21b075 --- /dev/null +++ b/src/components/drop-down-menu/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "composite": true, + "rootDir": "../../" + }, + "include": [ + "*.ts", + "../icon-control-bar/icon-control-bar.js", + "../header-bar/header-bar.js", + "./interface.js" + ] +} diff --git a/src/components/header-bar/header-bar.ts b/src/components/header-bar/header-bar.ts new file mode 100644 index 0000000..66351e4 --- /dev/null +++ b/src/components/header-bar/header-bar.ts @@ -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` +
+
+
+ +
+
+ ` + } + + getTimeAndDate(): HTMLTemplateResult { + return html` +
+
+
+
+
+
+ +
+
+ ` + } + + 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 (max-width: 1200px) { + .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 (max-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 + } +} diff --git a/src/components/header-bar/index.ts b/src/components/header-bar/index.ts new file mode 100644 index 0000000..313d237 --- /dev/null +++ b/src/components/header-bar/index.ts @@ -0,0 +1 @@ +export * from './header-bar.js' diff --git a/src/components/header-bar/package.json b/src/components/header-bar/package.json new file mode 100644 index 0000000..5b338bb --- /dev/null +++ b/src/components/header-bar/package.json @@ -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" +} diff --git a/src/components/header-bar/tsconfig.json b/src/components/header-bar/tsconfig.json new file mode 100644 index 0000000..4ba3e2c --- /dev/null +++ b/src/components/header-bar/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "composite": true, + "rootDir": "../../" + }, + "include": ["*.ts"] +} diff --git a/src/components/icon-control-bar-group/icon-control-bar-group.ts b/src/components/icon-control-bar-group/icon-control-bar-group.ts new file mode 100644 index 0000000..0b3492b --- /dev/null +++ b/src/components/icon-control-bar-group/icon-control-bar-group.ts @@ -0,0 +1,170 @@ +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` + + ` + : nothing + return html` +
+
+ +
+ ${colorstyle} +
+ ` + } + + getIconWithState(): HTMLTemplateResult { + return html` +
+ +
+ ` + } + + 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); + } + + // :host([deep-mode]) .icon-only > div > ::slotted(*) { + // width: 25%; + // // height: 30px; + // display: flex; + // align-items: center; + // justify-content: center; + // // border-left: 2px solid var(--line-border-dm); + // // border-radius: 2px; + // } + + @media screen and (max-width: 1200px) { + :host { + border-radius: 16px; + } + + .icon-only > div > ::slotted(*) { + height: 30px; + border-left: 2px solid var(--line-border-lm); + border-radius: 2px; + } + + :host([deep-mode]) { + border-radius: 16px; + } + + :host([deep-mode]) .icon-only > div > ::slotted(*) { + border-left: 2px solid var(--line-border-dm); + } + } + + @media screen and (max-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]) { + border-radius: 8px; + } + + :host([deep-mode]) .icon-only > div > ::slotted(*) { + border-left: 1px solid var(--line-border-dm); + } + } + ` +} + +declare global { + interface HTMLElementTagNameMap { + 'icon-control-bar-group': IconControlBarGroup + } +} diff --git a/src/components/icon-control-bar-group/index.ts b/src/components/icon-control-bar-group/index.ts new file mode 100644 index 0000000..0ea90fc --- /dev/null +++ b/src/components/icon-control-bar-group/index.ts @@ -0,0 +1 @@ +export * from './icon-control-bar-group.js' diff --git a/src/components/icon-control-bar-group/package.json b/src/components/icon-control-bar-group/package.json new file mode 100644 index 0000000..d6e21f0 --- /dev/null +++ b/src/components/icon-control-bar-group/package.json @@ -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" +} diff --git a/src/components/icon-control-bar-group/tsconfig.json b/src/components/icon-control-bar-group/tsconfig.json new file mode 100644 index 0000000..4ba3e2c --- /dev/null +++ b/src/components/icon-control-bar-group/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "composite": true, + "rootDir": "../../" + }, + "include": ["*.ts"] +} diff --git a/src/components/icon-control-bar/icon-control-bar.ts b/src/components/icon-control-bar/icon-control-bar.ts new file mode 100644 index 0000000..7fba0c1 --- /dev/null +++ b/src/components/icon-control-bar/icon-control-bar.ts @@ -0,0 +1,426 @@ +import {html, css, LitElement, HTMLTemplateResult, nothing} from 'lit' +import { + customElement, + property, + eventOptions, + query, + state, +} from 'lit/decorators.js' +import {WebActivity} from './interface.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 + @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` + + ` + + return html` +
+ ${iconstyle} +
+ ` + } + + getbaseWithOutBorder(): HTMLTemplateResult | typeof nothing { + if (!this.icon) { + console.error('【icon-control-bar】缺少 icon 参数') + return nothing + } + + return html` +
+ ` + } + + getwithstate(): HTMLTemplateResult | typeof nothing { + if (!this.icon) { + console.error('【icon-control-bar】缺少 icon 参数') + return nothing + } + + const iconstyle = html` + + ` + + return html` +
+

${this.stateDesc}

+
+ ${iconstyle} +
+ ` + } + + @eventOptions({passive: false}) + handleClick(event: Event) { + 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 = ""; + // } + // } + + let self = this + window.dispatchEvent( + new CustomEvent('icon-control-bar-click', { + detail: { + id: self.id ? self.id : self.icon, + isActive: isActive, + target: self, + }, + }) + ) + } + + handleInfo() { + let self = this + window.dispatchEvent( + new CustomEvent('more-info-action', {detail: self.icon}) + ) + } + + handlePress(event: Event) { + let target = event.target as HTMLElement + if (target.nodeName == 'P') { + target = target.parentElement as HTMLElement + } + + if (this.getAttribute('disabled') === 'true') { + return + } + + let section = this.icon + this.timer = setTimeout(() => { + let activity = new WebActivity('moz_configure_window', { + data: { + target: 'device', + section: section, + }, + }) + activity.start() + }, 300) + + let self = this + window.dispatchEvent( + new CustomEvent('touch-start', { + detail: { + id: self.id ? self.id : self.icon, + target: self, + }, + }) + ) + } + + handlePressEnd() { + clearTimeout(this.timer) + } + + 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; + } + + .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 (max-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; + } + + :host([deep-mode]) p { + width: 50px; + height: 10px; + position: relative; + left: 17px; + font-weight: 400; + font-size: 10.5px; + line-height: 10px; + } + + } + + + @media screen and (max-width: 1200px) { + .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 + } +} diff --git a/src/components/icon-control-bar/index.ts b/src/components/icon-control-bar/index.ts new file mode 100644 index 0000000..3aab161 --- /dev/null +++ b/src/components/icon-control-bar/index.ts @@ -0,0 +1 @@ +export * from './icon-control-bar.js' diff --git a/src/components/icon-control-bar/interface.ts b/src/components/icon-control-bar/interface.ts new file mode 100644 index 0000000..37700c0 --- /dev/null +++ b/src/components/icon-control-bar/interface.ts @@ -0,0 +1,9 @@ +export interface WebActivity { + start(): Promise + cancel(): void +} + +export var WebActivity: { + prototype: WebActivity + new (name: string, data?: any): WebActivity +} diff --git a/src/components/icon-control-bar/package.json b/src/components/icon-control-bar/package.json new file mode 100644 index 0000000..b4688f7 --- /dev/null +++ b/src/components/icon-control-bar/package.json @@ -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" +} diff --git a/src/components/icon-control-bar/tsconfig.json b/src/components/icon-control-bar/tsconfig.json new file mode 100644 index 0000000..f77af19 --- /dev/null +++ b/src/components/icon-control-bar/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "composite": true, + "rootDir": "../../" + }, + "include": ["*.ts", "./interface.js"] +} diff --git a/src/index.ts b/src/index.ts index f05b2b7..8ee91f6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,8 +15,8 @@ import './components/toast/toast' import './components/picker/picker' import './components/overflowmenu/overflowmenu' import './components/slider/slider' -import './components/control-center/icon-item' -import './components/control-center/icons-one-line' +import './components/icon-control-bar/icon-control-bar' +import './components/icon-control-bar-group/icon-control-bar-group' import './components/prompt/prompt' import './components/digicipher/digicipher' import './components/pattern-view/pattern-view' diff --git a/src/test/panels/control-center/control-center.ts b/src/test/panels/control-center/control-center.ts index ca8ffb2..866bfde 100644 --- a/src/test/panels/control-center/control-center.ts +++ b/src/test/panels/control-center/control-center.ts @@ -1,101 +1,117 @@ -import {html, css, LitElement} from 'lit' +import {html, LitElement} from 'lit' import {customElement, query} from 'lit/decorators.js' -import {IconItem, IconType} from "../../../components/control-center/icon-item" -import "../../../components/control-center/icon-item" -import {IconsOneLineType} from "../../../components/control-center/icons-one-line" -import {UlType} from "../../../components/ul/ul" -import {HeaderBarType} from "../../../components/control-center/header-bar" -import "../../../components/control-center/icons-one-line" -import "../../../components/ul/ul" -import "../../../components/control-center/header-bar" -import "../../../components/control-center/pull-down-menu" -import "../../../components/control-center/control-center-bar" +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-item[icon="wifi-4"]') wifiIcon!: LitElement; - @query('icon-item[icon="crop"]') cropBtn!: LitElement; - @query('icon-item[icon="brightness"]') brightnessBtn!: 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` - -

带状态的图标,点击有背景色变化,绑定settingsObserver监测值

- -
-

基本图标

- -
-

基本图标,点击有背景色变化

- -
-

基本图标,不带边框

- -
+ +

带状态的图标,点击有背景色变化,绑定settingsObserver监测值

+ +
+

基本图标

+ +
+

基本图标,点击有背景色变化

+ +
+

基本图标,不带边框

+ +
- -

一行带状态的图标

- - - - - -
-
-

一个box框,框内是由短竖线分隔开的基本图标

- - - - - - -
-
+ +

一行带状态的图标

+ + + + + +
+
+

一个box框,框内是由短竖线分隔开的基本图标

+ + + + + + +
+
- - -

header带时间

- - - - -
-

header带时间/日期/星期

- - - -
-
- - - - - - - - - ` - - // return html` - // - // - // - // - - // ` - - // return html` - // - // - // ` - } - - static styles = css` - control-center-bar { - width: 1200px; - height: 1920px; - } - `; - handleEvent(event: Event) { - console.log("click on icon item: " + (event.target as IconItem).icon); + +

header带时间

+ + + + +
+

header带时间/日期/星期

+ + + +
+
+ ` } } diff --git a/src/test/panels/root.ts b/src/test/panels/root.ts index 5fcb9c6..53204b3 100644 --- a/src/test/panels/root.ts +++ b/src/test/panels/root.ts @@ -119,9 +119,6 @@ export class PanelRoot extends LitElement {
>>>>>> 45627df8470c7bd87bfd6e38897cf3b896b0234d icon="scene" iconcolor="#EB7347" href="#slider" From 7c143bd607fff9a7ba7726213aadc1b396acc452 Mon Sep 17 00:00:00 2001 From: duanzhijiang Date: Sat, 24 Sep 2022 16:31:52 +0800 Subject: [PATCH 08/20] =?UTF-8?q?TASK:=20#111576=20=20=E5=9B=BE=E6=A1=88?= =?UTF-8?q?=E5=AF=86=E7=A0=81=E7=BB=84=E4=BB=B6=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/pattern-view/README.md | 32 +++ .../pattern-view/pattern-view-style.ts | 78 +++++++ src/components/pattern-view/pattern-view.ts | 190 ++++++++++++++++++ src/test/panels/pattern-view/pattern-view.ts | 27 +++ 4 files changed, 327 insertions(+) create mode 100644 src/components/pattern-view/README.md create mode 100644 src/components/pattern-view/pattern-view-style.ts create mode 100644 src/components/pattern-view/pattern-view.ts create mode 100644 src/test/panels/pattern-view/pattern-view.ts diff --git a/src/components/pattern-view/README.md b/src/components/pattern-view/README.md new file mode 100644 index 0000000..7cb23a0 --- /dev/null +++ b/src/components/pattern-view/README.md @@ -0,0 +1,32 @@ +# 图案密码 pattern-view + + +工作职责: + +- 由九宫圆圈组成的图案密码 +- 默认密码为 `012543` (滑动如下图) +``` + --- --- --- + | * | **** | * | **** | * | + --- --- --- + * + --- --- --- + | * | **** | * | **** | * | + --- --- --- + + --- --- --- + | | | | | | + --- --- --- +``` +- 点击数字反馈,输入成功上滑,输入错误抖动反馈 + +### 默认 +``` + +``` +### 距离顶部的位置 `topDir` 默认`217.5px` +``` + + + +``` \ No newline at end of file diff --git a/src/components/pattern-view/pattern-view-style.ts b/src/components/pattern-view/pattern-view-style.ts new file mode 100644 index 0000000..94fa614 --- /dev/null +++ b/src/components/pattern-view/pattern-view-style.ts @@ -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; +} */ +` diff --git a/src/components/pattern-view/pattern-view.ts b/src/components/pattern-view/pattern-view.ts new file mode 100644 index 0000000..7337210 --- /dev/null +++ b/src/components/pattern-view/pattern-view.ts @@ -0,0 +1,190 @@ +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` + + + ` + } + + protected firstUpdated( + _changedProperties: PropertyValueMap | Map + ): 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 { + // console.log('密码错误', this.pwdArr.toString()) + // this.Draw(null) + // } + this.getRed = true + this.pwdArr = [] + this.dispatchEvent( + new TouchEvent('end', { + composed: true, + }) + ) + return this.passwdArr + } +} diff --git a/src/test/panels/pattern-view/pattern-view.ts b/src/test/panels/pattern-view/pattern-view.ts new file mode 100644 index 0000000..79d27d6 --- /dev/null +++ b/src/test/panels/pattern-view/pattern-view.ts @@ -0,0 +1,27 @@ +import {html, LitElement, css} from 'lit' +import {customElement, property} 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` + + + ` + } +} + +declare global { + interface HTMLElementTagNameMap { + 'panel-pattern-view': PanelPatternView + } +} From 00240af2459d330dd84cc5f7c5cbb2c64cb6212e Mon Sep 17 00:00:00 2001 From: yajun Date: Mon, 26 Sep 2022 15:41:26 +0800 Subject: [PATCH 09/20] =?UTF-8?q?TASK:=20#110347=20overflowmenu=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/button/button.ts | 4 ++ src/components/overlay/active-overlay.ts | 36 +++++------ src/components/overlay/overlay-stack.ts | 15 +++-- src/components/ul/ul.ts | 7 +++ src/test/panels/overflowmenu/overflowmenu.ts | 63 ++++++++++---------- 5 files changed, 64 insertions(+), 61 deletions(-) diff --git a/src/components/button/button.ts b/src/components/button/button.ts index a5e7093..d41f6f9 100644 --- a/src/components/button/button.ts +++ b/src/components/button/button.ts @@ -121,6 +121,10 @@ export class StarButton extends StarBaseElement { align-items: center; margin: auto; } + :host(#menuitem) button { + font-size: 24px; + height: 76px; + } `, ] } diff --git a/src/components/overlay/active-overlay.ts b/src/components/overlay/active-overlay.ts index 8746c37..e79e441 100644 --- a/src/components/overlay/active-overlay.ts +++ b/src/components/overlay/active-overlay.ts @@ -1,11 +1,10 @@ import {LitElement, html, TemplateResult, CSSResultArray} from 'lit' import {customElement, property} from 'lit/decorators.js' import {OverlayOpenDetail} from './overlay-types' -import { sharedStyles } from './overlaystyle' +import {sharedStyles} from './overlaystyle' @customElement('star-activeoverlay') export class ActiveOverlay extends LitElement { - public static override get styles(): CSSResultArray { return [sharedStyles] } @@ -20,9 +19,6 @@ export class ActiveOverlay extends LitElement { @property({reflect: true}) public placement?: String - offset!: number - skidding!: number - public constructor() { super() } @@ -37,8 +33,6 @@ export class ActiveOverlay extends LitElement { this.root = detail.root } - private stealOverlayContent(element: HTMLElement) {} - // 位置更新函数 public updatePosition = () => { // 设置最大显示宽度和高度 @@ -57,38 +51,36 @@ export class ActiveOverlay extends LitElement { 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 = '10' this.style.maxWidth = availableWidth + 'px' this.style.maxHeight = availableHeight + 'px' // 边界判断:targetright指被点击的节点右边界,contentwidth表示将要显示的内容的宽度 - const rightline = targetright + contentwidth > bodywidth ? true : false // 右侧越界条件 + 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 - contentheight - 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 - targetnode.offsetHeight + '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 { diff --git a/src/components/overlay/overlay-stack.ts b/src/components/overlay/overlay-stack.ts index 5116cdd..1d7af3e 100644 --- a/src/components/overlay/overlay-stack.ts +++ b/src/components/overlay/overlay-stack.ts @@ -23,17 +23,17 @@ export class OverlayStack { activeOverlay.root?.appendChild(placeholderTemplate) // // 将activeoverlay添加到body底部显示div中 let showmenu = document.querySelector('#showmenu') as HTMLElement //后续关联模态时 - if(showmenu==null){ + if (showmenu == null) { showmenu = document.createElement('div') - showmenu.setAttribute('id','showmenu') + showmenu.setAttribute('id', 'showmenu') Object.assign(showmenu.style, { width: '100vw', height: '100vh', position: 'fixed', backgroundColor: '#00000033', justifycontent: 'center', - }); - }else{ + }) + } else { showmenu.style.display = 'fixed' } // 为显示div添加overlay并将div添加到body中 @@ -42,9 +42,9 @@ export class OverlayStack { // 将activeoverlay添加到已打开overlay数组中 this.overlays.push(activeOverlay) // 为showmenu显示层添加监听 - showmenu.addEventListener('click',function(e:Event){ - console.log(e.target); - if(e.target == this){ + showmenu.addEventListener('click', function (e: Event) { + console.log(e.target) + if (e.target == this) { root!.dispatchEvent( new Event('test', { bubbles: true, @@ -67,7 +67,6 @@ export class OverlayStack { const placeholder = srcroot?.lastChild as Node // 获取overlay中的内容 const content = closeactiveoverlay.restoreContent as HTMLElement - console.log(content.offsetWidth); // 替换子节点 srcroot?.replaceChild(content, placeholder) const showmenu = document.querySelector('#showmenu') as HTMLElement diff --git a/src/components/ul/ul.ts b/src/components/ul/ul.ts index ebeb22e..65053d3 100644 --- a/src/components/ul/ul.ts +++ b/src/components/ul/ul.ts @@ -116,6 +116,13 @@ export class StarUl extends LitElement { max-width: 88vw; } + :host(#iconmenu) { + width: 320px; + display: block; + margin: 0; + border-radius: 16px; + } + ul { list-style: none; flex-direction: column; diff --git a/src/test/panels/overflowmenu/overflowmenu.ts b/src/test/panels/overflowmenu/overflowmenu.ts index 8f431ba..bc27d84 100644 --- a/src/test/panels/overflowmenu/overflowmenu.ts +++ b/src/test/panels/overflowmenu/overflowmenu.ts @@ -15,26 +15,24 @@ export class PanelOverflowMenu extends LitElement { return html` @@ -52,26 +51,24 @@ export class PanelOverflowMenu extends LitElement { style="position: fixed; top: 50%; left: 50%;" > - + @@ -123,26 +123,24 @@ export class PanelOverflowMenu extends LitElement { style="position: fixed; top: 90%; left: 0;" > @@ -160,26 +159,24 @@ export class PanelOverflowMenu extends LitElement { style="position: fixed; top: 90%; left: 90%;" > @@ -215,6 +213,9 @@ export class PanelOverflowMenu extends LitElement { star-li span.split { color: var(--split-line); } + ::slotted(div) { + border-radius: 16px; + } `, ] } From fd509c8b813483f6312f4163ff467ed1eee26ead Mon Sep 17 00:00:00 2001 From: yajun Date: Mon, 26 Sep 2022 15:54:59 +0800 Subject: [PATCH 10/20] =?UTF-8?q?=09TASK:=20#110347=20=E5=A4=84=E7=90=86?= =?UTF-8?q?=E5=86=B2=E7=AA=81=E5=8F=8A=E6=A0=B7=E5=BC=8F=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/button/button.css.ts | 6 ++++++ src/components/ul/ul.css.ts | 8 ++++++++ src/test/panels/activeoverlay/activeoverlay.ts | 3 +++ 3 files changed, 17 insertions(+) diff --git a/src/components/button/button.css.ts b/src/components/button/button.css.ts index 1e3c019..385345d 100644 --- a/src/components/button/button.css.ts +++ b/src/components/button/button.css.ts @@ -102,4 +102,10 @@ export default css` align-items: center; margin: auto; } + + /* menubutton */ + :host(#menuitem) button { + font-size: 24px; + height: 76px; + } ` diff --git a/src/components/ul/ul.css.ts b/src/components/ul/ul.css.ts index a0bff9a..167be04 100644 --- a/src/components/ul/ul.css.ts +++ b/src/components/ul/ul.css.ts @@ -8,6 +8,14 @@ export default css` max-width: 88vw; } + /* menu ul */ + :host(#iconmenu) { + width: 320px; + display: block; + margin: 0; + border-radius: 16px; + } + ul { list-style: none; flex-direction: column; diff --git a/src/test/panels/activeoverlay/activeoverlay.ts b/src/test/panels/activeoverlay/activeoverlay.ts index 92ca5e1..42be2cc 100644 --- a/src/test/panels/activeoverlay/activeoverlay.ts +++ b/src/test/panels/activeoverlay/activeoverlay.ts @@ -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 From 31f5e51c83b0fbf1961c7159bcc6432d4fcce6a3 Mon Sep 17 00:00:00 2001 From: wangchangqi Date: Mon, 26 Sep 2022 16:26:26 +0800 Subject: [PATCH 11/20] (chore)update some README and test file --- src/components/grabber/README.md | 6 ++ src/components/grabber/home-bar-indicator.ts | 64 ++------------------ src/components/pattern-view/pattern-view.ts | 2 +- src/lib/gesture/README.md | 36 +++++++++-- src/lib/gesture/TODO.md | 7 +++ src/lib/gesture/gesture-detector.ts | 46 +++----------- src/test/panels/indicators/home-indicator.ts | 37 +++++------ src/test/panels/root.ts | 18 +++--- 8 files changed, 87 insertions(+), 129 deletions(-) create mode 100644 src/lib/gesture/TODO.md diff --git a/src/components/grabber/README.md b/src/components/grabber/README.md index 8a78b89..84f8831 100644 --- a/src/components/grabber/README.md +++ b/src/components/grabber/README.md @@ -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 区域。 + 主屏指示器应具备的功能: - 锁屏处,向上拨动底部可抓取的主屏指示器随手指拖动向上推动锁屏页,进入主屏页 diff --git a/src/components/grabber/home-bar-indicator.ts b/src/components/grabber/home-bar-indicator.ts index c4b5a3f..de7b781 100644 --- a/src/components/grabber/home-bar-indicator.ts +++ b/src/components/grabber/home-bar-indicator.ts @@ -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; - } `, ] } diff --git a/src/components/pattern-view/pattern-view.ts b/src/components/pattern-view/pattern-view.ts index 7337210..077b4c5 100644 --- a/src/components/pattern-view/pattern-view.ts +++ b/src/components/pattern-view/pattern-view.ts @@ -169,7 +169,7 @@ export class StarPatternView extends LitElement { Y: touches.pageY - this.top, }) } - touchEnd(e: TouchEvent) { + touchEnd(_: TouchEvent) { this.cxt.clearRect(0, 0, this.canvasWidth, this.canvasHeight) this.Draw() // if (this.pwdArr.toString() == this.passwdArr.toString()) { diff --git a/src/lib/gesture/README.md b/src/lib/gesture/README.md index 3210a86..9fe0a16 100644 --- a/src/lib/gesture/README.md +++ b/src/lib/gesture/README.md @@ -81,16 +81,16 @@ class MyElement extends StarBaseElement {

FSM状态转移表

-| 当前状态 →
条件 ↓ | initialState | touchStartedState | touchesStartedState | holdState | panState | swipeState | pinchState | rotateState | -| ---------------------------------- | ----------------- | ------------------- | ------------------- | ------------ | ---------- | ------------ | ------------ | ------------ | +| 当前状态 →
条件 ↓ | 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 +224,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 +) +``` diff --git a/src/lib/gesture/TODO.md b/src/lib/gesture/TODO.md new file mode 100644 index 0000000..d9a9c0f --- /dev/null +++ b/src/lib/gesture/TODO.md @@ -0,0 +1,7 @@ +# TODO + +1. 单击位移阈值 10px 或热区以内 +2. 双击间隔时间小于 300ms,两次点击的手指位移阈值 10px +3. 长按的位移阈值 10px +4. 拖放判定条件:手指进入长按状态后,手指位移阈值 10px +5. 滑动、轻扫判定条件:手指位移阈值 20px diff --git a/src/lib/gesture/gesture-detector.ts b/src/lib/gesture/gesture-detector.ts index 4ed3e00..12bbb82 100644 --- a/src/lib/gesture/gesture-detector.ts +++ b/src/lib/gesture/gesture-detector.ts @@ -99,8 +99,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 +221,6 @@ export type GestureState = | 'panState' | 'holdState' | 'swipeState' - | 'rotateState' - | 'pinchState' | 'transformState' type PickUP = T[K] @@ -733,31 +730,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 +812,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) { @@ -1124,6 +1102,7 @@ export default class GestureDector { // 清理资源 this.lastPanTouchID = -1 + // 如果检测到是第2指,提前退出 if (isSecondFinger) { debugInfo('swipe::isSecondFinger', this.vc) if ( @@ -1201,15 +1180,6 @@ export default class GestureDector { touchcancel: null, } - // private rotateState: FSMGestureState = { - // name: 'rotateState', - // init: () => {}, - // touchstart: null, - // touchmove: null, - // touchend: null, - // touchcancel: null, - // } - // TODO: 优化调用处,添加缓存处理 private getSeriesEvent( type: T diff --git a/src/test/panels/indicators/home-indicator.ts b/src/test/panels/indicators/home-indicator.ts index e13c769..643d138 100644 --- a/src/test/panels/indicators/home-indicator.ts +++ b/src/test/panels/indicators/home-indicator.ts @@ -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` - + ` } diff --git a/src/test/panels/root.ts b/src/test/panels/root.ts index 192b411..f8a6115 100644 --- a/src/test/panels/root.ts +++ b/src/test/panels/root.ts @@ -241,14 +241,6 @@ export class PanelRoot extends LitElement { href="#indicators" >

- -
- + +
+
From b31bc2bb682ef11c5cad94ac2d846494ceedff5b Mon Sep 17 00:00:00 2001 From: duanzhijiang Date: Mon, 26 Sep 2022 16:39:12 +0800 Subject: [PATCH 12/20] =?UTF-8?q?TASK:=20#111576=20=E6=95=B0=E5=AD=97?= =?UTF-8?q?=E5=AF=86=E7=A0=81=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/digicipher/digicipher-style.ts | 47 ++++++++++++------- src/components/digicipher/digicipher.ts | 13 +++-- src/test/panels/digicipher/digicipher.ts | 4 +- 3 files changed, 41 insertions(+), 23 deletions(-) diff --git a/src/components/digicipher/digicipher-style.ts b/src/components/digicipher/digicipher-style.ts index c422504..20139e4 100644 --- a/src/components/digicipher/digicipher-style.ts +++ b/src/components/digicipher/digicipher-style.ts @@ -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,12 +44,11 @@ export const sharedStyles: CSSResult = css` } #zero { - position: absolute; - left: 44.15%; - top: 70%; + position: relative; + left: 42.5%; + top: 68%; } button { - position: relative; width: 70px; height: 70px; border-radius: 50%; @@ -71,8 +79,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,21 +94,21 @@ 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; + position: relative; 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 118px; } .cancel, .delete { - position: absolute; + position: relative; font-family: 'OPPOSans'; font-style: normal; font-weight: 400; @@ -108,12 +119,12 @@ export const sharedStyles: CSSResult = css` font-size: 18px; } .cancel { - left: calc(50% - 36px / 2 - 90px); - top: calc(50% - 23.5px / 2 + 231.75px); + left: 27%; + top: 5%; } .delete { - left: calc(50% - 36px / 2 + 90px); - top: calc(50% - 23.5px / 2 + 231.75px); + left: 65.5%; + top: -3.5%; } .cancel:hover, .delete:hover { diff --git a/src/components/digicipher/digicipher.ts b/src/components/digicipher/digicipher.ts index 6c557bd..c8210d6 100644 --- a/src/components/digicipher/digicipher.ts +++ b/src/components/digicipher/digicipher.ts @@ -18,6 +18,8 @@ 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}) clicks = 0 @property({type: Number}) _number = 0 @property({type: Number}) clickNumber = 0 @@ -64,8 +66,6 @@ export class StarLockNumber extends LitElement { 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 +81,14 @@ export class StarLockNumber extends LitElement { } //密码错误 if (this.clicks == 6 && this.guess !== this.passwd) { - console.log('密码错误') + //输错密码5次后锁定屏幕 + if (this.tried <= 5) { + this.tried++ + console.log('密码错误 已尝试' + this.tried + '次') + this.locked = this.tried >= 5 ? true : false + } else { + this.tried = 0 + } // 抖动反馈 for (let i = 0; i < 10; i++) { this.buttons[i].style.setProperty('animation', 'errtips .5s') diff --git a/src/test/panels/digicipher/digicipher.ts b/src/test/panels/digicipher/digicipher.ts index 2014cf8..e96aa7d 100644 --- a/src/test/panels/digicipher/digicipher.ts +++ b/src/test/panels/digicipher/digicipher.ts @@ -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() { From d7894eb701746c244067e23fc097c4c74ce23b98 Mon Sep 17 00:00:00 2001 From: duanzhijiang Date: Mon, 26 Sep 2022 16:42:45 +0800 Subject: [PATCH 13/20] =?UTF-8?q?TASK:=20#111576=20=E5=9B=BE=E6=A1=88?= =?UTF-8?q?=E5=AF=86=E7=A0=81=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/pattern-view/pattern-view.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/pattern-view/pattern-view.ts b/src/components/pattern-view/pattern-view.ts index 7337210..3446e03 100644 --- a/src/components/pattern-view/pattern-view.ts +++ b/src/components/pattern-view/pattern-view.ts @@ -169,14 +169,15 @@ export class StarPatternView extends LitElement { Y: touches.pageY - this.top, }) } - touchEnd(e: TouchEvent) { + 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(null) + // this.Draw() // } this.getRed = true this.pwdArr = [] From 9ee4ea8e5d0a318e65d5b3a8e346e099024e827b Mon Sep 17 00:00:00 2001 From: duanzhijiang Date: Mon, 26 Sep 2022 16:44:00 +0800 Subject: [PATCH 14/20] =?UTF-8?q?TASK:=20#111576=20=E5=B7=B2=E8=A7=A3?= =?UTF-8?q?=E9=94=81=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/panels/locked/locked.ts | 111 +++++++++++++++++++++++++++++++ src/test/panels/root.ts | 15 ++++- 2 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 src/test/panels/locked/locked.ts diff --git a/src/test/panels/locked/locked.ts b/src/test/panels/locked/locked.ts new file mode 100644 index 0000000..f1dda32 --- /dev/null +++ b/src/test/panels/locked/locked.ts @@ -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` +
+
+
+
+
+
+
+
+
已锁定
+
请${this.second}秒后再试
+
+ ` + } + + protected firstUpdated() { + setInterval(() => { + if (--this.second <= 0) { + this.second = 30 + } + }, 1000) + } +} + +declare global { + interface HTMLElementTagNameMap { + 'panel-locked': PanelLocked + } +} diff --git a/src/test/panels/root.ts b/src/test/panels/root.ts index 4cb3221..6dffe4c 100644 --- a/src/test/panels/root.ts +++ b/src/test/panels/root.ts @@ -23,6 +23,7 @@ import './overflowmenu/overflowmenu' import './switch/switch' import './slider/slider' import './digicipher/digicipher' +import './locked/locked' import './pattern-view/pattern-view' import './container/homescreen-container' import './toast/toast' @@ -131,14 +132,14 @@ export class PanelRoot extends LitElement {
+
+ Date: Tue, 27 Sep 2022 11:06:18 +0800 Subject: [PATCH 15/20] =?UTF-8?q?=E4=BF=AE=E6=94=B9icon-control-bar?= =?UTF-8?q?=E7=9B=B8=E5=85=B3=E4=BA=8B=E4=BB=B6=E7=AD=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../drop-down-menu/drop-down-menu.ts | 31 +----- src/components/drop-down-menu/interface.ts | 9 -- src/components/drop-down-menu/tsconfig.json | 3 +- .../icon-control-bar/icon-control-bar.ts | 105 ++++++++++++------ src/components/icon-control-bar/interface.ts | 9 -- src/components/icon-control-bar/tsconfig.json | 2 +- 6 files changed, 76 insertions(+), 83 deletions(-) delete mode 100644 src/components/drop-down-menu/interface.ts delete mode 100644 src/components/icon-control-bar/interface.ts diff --git a/src/components/drop-down-menu/drop-down-menu.ts b/src/components/drop-down-menu/drop-down-menu.ts index efe1723..6a7470e 100644 --- a/src/components/drop-down-menu/drop-down-menu.ts +++ b/src/components/drop-down-menu/drop-down-menu.ts @@ -1,11 +1,7 @@ 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 { - IconControlBar, - IconControlBarType, -} from '../icon-control-bar/icon-control-bar.js' -import {WebActivity} from './interface.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 { @@ -22,14 +18,14 @@ export class DropDownMenu extends LitElement {
@@ -44,9 +40,9 @@ export class DropDownMenu extends LitElement {
@@ -145,25 +141,6 @@ export class DropDownMenu extends LitElement { } } ` - - handleEvent(event: Event) { - switch (event.type) { - case 'click': - switch ((event.target as IconControlBar).icon) { - case 'compose': - window.dispatchEvent(new CustomEvent('editor-action')) - break - case 'settings': - let activity = new WebActivity('configure', {}) - activity.start().then( - () => {}, - () => {} - ) - break - } - break - } - } } declare global { diff --git a/src/components/drop-down-menu/interface.ts b/src/components/drop-down-menu/interface.ts deleted file mode 100644 index 37700c0..0000000 --- a/src/components/drop-down-menu/interface.ts +++ /dev/null @@ -1,9 +0,0 @@ -export interface WebActivity { - start(): Promise - cancel(): void -} - -export var WebActivity: { - prototype: WebActivity - new (name: string, data?: any): WebActivity -} diff --git a/src/components/drop-down-menu/tsconfig.json b/src/components/drop-down-menu/tsconfig.json index f21b075..bb310d6 100644 --- a/src/components/drop-down-menu/tsconfig.json +++ b/src/components/drop-down-menu/tsconfig.json @@ -7,7 +7,6 @@ "include": [ "*.ts", "../icon-control-bar/icon-control-bar.js", - "../header-bar/header-bar.js", - "./interface.js" + "../header-bar/header-bar.js" ] } diff --git a/src/components/icon-control-bar/icon-control-bar.ts b/src/components/icon-control-bar/icon-control-bar.ts index 2ee285e..05235d1 100644 --- a/src/components/icon-control-bar/icon-control-bar.ts +++ b/src/components/icon-control-bar/icon-control-bar.ts @@ -6,7 +6,6 @@ import { query, state, } from 'lit/decorators.js' -import {WebActivity} from './interface.js' export enum IconControlBarType { BASE = 'base', @@ -23,6 +22,7 @@ export class IconControlBar extends LitElement { @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 @@ -56,9 +56,9 @@ export class IconControlBar extends LitElement {
${iconstyle}
@@ -75,9 +75,9 @@ export class IconControlBar extends LitElement {
` } @@ -110,9 +110,9 @@ export class IconControlBar extends LitElement {

${this.stateDesc}

{ - let activity = new WebActivity('moz_configure_window', { - data: { - target: 'device', - section: section, - }, - }) - activity.start() + 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) - - let self = this - window.dispatchEvent( - new CustomEvent('touch-start', { - detail: { - id: self.id ? self.id : self.icon, - target: self, - }, - }) - ) } 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) { @@ -260,6 +294,7 @@ export class IconControlBar extends LitElement { .active { background-color: var(--background-active) !important; color: var(--text-color-active) !important; + transform: scale(0.9); } .with-border { diff --git a/src/components/icon-control-bar/interface.ts b/src/components/icon-control-bar/interface.ts deleted file mode 100644 index 37700c0..0000000 --- a/src/components/icon-control-bar/interface.ts +++ /dev/null @@ -1,9 +0,0 @@ -export interface WebActivity { - start(): Promise - cancel(): void -} - -export var WebActivity: { - prototype: WebActivity - new (name: string, data?: any): WebActivity -} diff --git a/src/components/icon-control-bar/tsconfig.json b/src/components/icon-control-bar/tsconfig.json index f77af19..4ba3e2c 100644 --- a/src/components/icon-control-bar/tsconfig.json +++ b/src/components/icon-control-bar/tsconfig.json @@ -4,5 +4,5 @@ "composite": true, "rootDir": "../../" }, - "include": ["*.ts", "./interface.js"] + "include": ["*.ts"] } From a5471be9bae4a856c206e666299c4370f7bcab5e Mon Sep 17 00:00:00 2001 From: duanzhijiang Date: Tue, 27 Sep 2022 11:19:32 +0800 Subject: [PATCH 16/20] =?UTF-8?q?TASK:=20#111576=20=E6=95=B0=E5=AD=97?= =?UTF-8?q?=E5=AF=86=E7=A0=812=E5=88=86=E9=92=9F=E5=86=85=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E7=94=A8=E6=88=B7=E8=BE=93=E5=85=A5=E6=AC=A1=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/digicipher/digicipher-style.ts | 14 ++++---- src/components/digicipher/digicipher.ts | 36 ++++++++++++++++--- 2 files changed, 39 insertions(+), 11 deletions(-) diff --git a/src/components/digicipher/digicipher-style.ts b/src/components/digicipher/digicipher-style.ts index 20139e4..49c6561 100644 --- a/src/components/digicipher/digicipher-style.ts +++ b/src/components/digicipher/digicipher-style.ts @@ -49,6 +49,7 @@ export const sharedStyles: CSSResult = css` top: 68%; } button { + position: relative; width: 70px; height: 70px; border-radius: 50%; @@ -99,16 +100,16 @@ export const sharedStyles: CSSResult = css` top: 23%; } .grid { - position: relative; + position: absolute; display: grid; grid-template-areas: '1 2 3 ' '4 5 6' '7 8 9' '. 0 .'; gap: 20px; - margin: 0 118px; + margin: 0 124px; } .cancel, .delete { - position: relative; + position: absolute; font-family: 'OPPOSans'; font-style: normal; font-weight: 400; @@ -117,14 +118,13 @@ export const sharedStyles: CSSResult = css` height: 23.5px; line-height: 23.5px; font-size: 18px; + top: 73%; } .cancel { left: 27%; - top: 5%; } .delete { left: 65.5%; - top: -3.5%; } .cancel:hover, .delete:hover { @@ -173,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%); } } ` diff --git a/src/components/digicipher/digicipher.ts b/src/components/digicipher/digicipher.ts index c8210d6..312dfa9 100644 --- a/src/components/digicipher/digicipher.ts +++ b/src/components/digicipher/digicipher.ts @@ -20,7 +20,10 @@ export class StarLockNumber extends LitElement { @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 = '' @@ -28,6 +31,10 @@ export class StarLockNumber extends LitElement { @property({type: String}) guess = '' @property({type: String}) passwd = '123456' + constructor() { + super() + } + render() { return html`
@@ -63,6 +70,16 @@ export class StarLockNumber extends LitElement {
` } + timer() { + this.time = setInterval(() => { + if (--this.second <= 0) { + clearInterval(this.time) + this.second = 120 + this.tried = 0 + } + console.log(this.second) + }, 1000) + } touchStart(e: TouchEvent) { e.preventDefault() @@ -81,12 +98,20 @@ export class StarLockNumber extends LitElement { } //密码错误 if (this.clicks == 6 && this.guess !== this.passwd) { + //第一次密码错误开始计时120秒 + if (this.tried == 0) { + this.second = 120 + this.timer() + } //输错密码5次后锁定屏幕 - if (this.tried <= 5) { - this.tried++ - console.log('密码错误 已尝试' + this.tried + '次') - this.locked = this.tried >= 5 ? true : false + 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 } // 抖动反馈 @@ -119,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') } From 7071ed65742f87fa2008d60d0624a13ab1377f37 Mon Sep 17 00:00:00 2001 From: wangchangqi Date: Tue, 27 Sep 2022 11:33:52 +0800 Subject: [PATCH 17/20] (improve)Supprt velocity in panend event. --- src/lib/gesture/README.md | 2 ++ src/lib/gesture/gesture-detector.ts | 25 ++++++++++++++++++++++++- src/lib/gesture/package.json | 6 +++--- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/lib/gesture/README.md b/src/lib/gesture/README.md index 9fe0a16..f0a92b0 100644 --- a/src/lib/gesture/README.md +++ b/src/lib/gesture/README.md @@ -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 () {}) diff --git a/src/lib/gesture/gesture-detector.ts b/src/lib/gesture/gesture-detector.ts index 12bbb82..b735070 100644 --- a/src/lib/gesture/gesture-detector.ts +++ b/src/lib/gesture/gesture-detector.ts @@ -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 + } } /** @@ -1016,11 +1027,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 @@ -1072,6 +1094,7 @@ export default class GestureDector { Object.freeze({ endTouches: evt.touches, fingers: evt.touches.length, + velocity: this.vc, }) ) } diff --git a/src/lib/gesture/package.json b/src/lib/gesture/package.json index 965a204..6ae857d 100644 --- a/src/lib/gesture/package.json +++ b/src/lib/gesture/package.json @@ -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" From b6d1047169261cd4fdf1d138abf5f7b8780c9bad Mon Sep 17 00:00:00 2001 From: luojiahao Date: Mon, 26 Sep 2022 19:36:07 +0800 Subject: [PATCH 18/20] TASK: #113126 - add gauss blur component --- CHANGELOG.md | 1 + public/index.html | 2 +- public/manifest.webmanifest | 5 + src/components/gauss_canvas/README.md | 62 ++++ src/components/gauss_canvas/index.ts | 287 ++++++++++++++++++ src/components/gauss_canvas/package.json | 25 ++ src/components/gauss_canvas/worker.ts | 181 +++++++++++ .../panels/activeoverlay/activeoverlay.ts | 2 +- src/test/panels/digicipher/digicipher.ts | 2 +- src/test/panels/gauss_canvas/gauss-blur.ts | 127 ++++++++ src/test/panels/overflowmenu/overflowmenu.ts | 2 +- src/test/panels/root.ts | 9 + 12 files changed, 701 insertions(+), 4 deletions(-) create mode 100644 src/components/gauss_canvas/README.md create mode 100644 src/components/gauss_canvas/index.ts create mode 100644 src/components/gauss_canvas/package.json create mode 100644 src/components/gauss_canvas/worker.ts create mode 100644 src/test/panels/gauss_canvas/gauss-blur.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d02da7..cd1f809 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/public/index.html b/public/index.html index 1010c03..256f79a 100644 --- a/public/index.html +++ b/public/index.html @@ -8,7 +8,7 @@ content="width=device-width, user-scalable=no, initial-scale=1.0" /> Star Web Components - + diff --git a/public/manifest.webmanifest b/public/manifest.webmanifest index 3f7056d..97b9938 100644 --- a/public/manifest.webmanifest +++ b/public/manifest.webmanifest @@ -21,6 +21,11 @@ "description": "StarWeb组件" } }, + "permissions": { + "settings": { + "access": "readwrite" + } + }, "core": true, "version": "0.0.1" } diff --git a/src/components/gauss_canvas/README.md b/src/components/gauss_canvas/README.md new file mode 100644 index 0000000..c425bff --- /dev/null +++ b/src/components/gauss_canvas/README.md @@ -0,0 +1,62 @@ +## 高斯模糊小组件 + +小组件为解决用 `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 + +``` + +```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; + } +} +``` diff --git a/src/components/gauss_canvas/index.ts b/src/components/gauss_canvas/index.ts new file mode 100644 index 0000000..23ce167 --- /dev/null +++ b/src/components/gauss_canvas/index.ts @@ -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` + + ` + } + + 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 + } +} diff --git a/src/components/gauss_canvas/package.json b/src/components/gauss_canvas/package.json new file mode 100644 index 0000000..f852ed3 --- /dev/null +++ b/src/components/gauss_canvas/package.json @@ -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" +} diff --git a/src/components/gauss_canvas/worker.ts b/src/components/gauss_canvas/worker.ts new file mode 100644 index 0000000..978be46 --- /dev/null +++ b/src/components/gauss_canvas/worker.ts @@ -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) diff --git a/src/test/panels/activeoverlay/activeoverlay.ts b/src/test/panels/activeoverlay/activeoverlay.ts index 42be2cc..65bd198 100644 --- a/src/test/panels/activeoverlay/activeoverlay.ts +++ b/src/test/panels/activeoverlay/activeoverlay.ts @@ -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' diff --git a/src/test/panels/digicipher/digicipher.ts b/src/test/panels/digicipher/digicipher.ts index 2014cf8..34b61a9 100644 --- a/src/test/panels/digicipher/digicipher.ts +++ b/src/test/panels/digicipher/digicipher.ts @@ -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') diff --git a/src/test/panels/gauss_canvas/gauss-blur.ts b/src/test/panels/gauss_canvas/gauss-blur.ts new file mode 100644 index 0000000..34b9ddc --- /dev/null +++ b/src/test/panels/gauss_canvas/gauss-blur.ts @@ -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` +
+ + +
+ ` + } + + 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 + } +} diff --git a/src/test/panels/overflowmenu/overflowmenu.ts b/src/test/panels/overflowmenu/overflowmenu.ts index bc27d84..267004e 100644 --- a/src/test/panels/overflowmenu/overflowmenu.ts +++ b/src/test/panels/overflowmenu/overflowmenu.ts @@ -1,5 +1,5 @@ import {html, LitElement, css, CSSResultArray} from 'lit' -import {customElement, property} from 'lit/decorators.js' +import {customElement} from 'lit/decorators.js' import '../../../components/button/button' import '../../../components/ul/ul' import '../../../components/li//li' diff --git a/src/test/panels/root.ts b/src/test/panels/root.ts index f8a6115..1bef929 100644 --- a/src/test/panels/root.ts +++ b/src/test/panels/root.ts @@ -14,6 +14,7 @@ import './card/card' import './indicators/indicators' import './indicators/home-indicator' import './blur/use-blur' +import './gauss_canvas/gauss-blur' import './button/button' import './container/container' import './radio/radio' @@ -249,6 +250,14 @@ export class PanelRoot extends LitElement { href="#blur" >
+ +
Date: Tue, 27 Sep 2022 16:42:02 +0800 Subject: [PATCH 19/20] TASK: #105604-add indicator-page-deformation component. --- src/components/digicipher/digicipher.ts | 2 +- src/components/gauss_canvas/README.md | 57 ++++--- .../indicator/indicator-page-deformation.ts | 134 +++++++++++++++++ src/test/panels/indicators/README.md | 13 ++ .../indicators/deformation-indicator.ts | 140 ++++++++++++++++++ .../{indicators.ts => point-indicator.ts} | 6 +- src/test/panels/root.ts | 29 ++-- src/test/panels/switch/switch.ts | 2 +- 8 files changed, 340 insertions(+), 43 deletions(-) create mode 100644 src/components/indicator/indicator-page-deformation.ts create mode 100644 src/test/panels/indicators/README.md create mode 100644 src/test/panels/indicators/deformation-indicator.ts rename src/test/panels/indicators/{indicators.ts => point-indicator.ts} (96%) diff --git a/src/components/digicipher/digicipher.ts b/src/components/digicipher/digicipher.ts index 312dfa9..3401736 100644 --- a/src/components/digicipher/digicipher.ts +++ b/src/components/digicipher/digicipher.ts @@ -71,7 +71,7 @@ export class StarLockNumber extends LitElement { ` } timer() { - this.time = setInterval(() => { + this.time = window.setInterval(() => { if (--this.second <= 0) { clearInterval(this.time) this.second = 120 diff --git a/src/components/gauss_canvas/README.md b/src/components/gauss_canvas/README.md index c425bff..178bf16 100644 --- a/src/components/gauss_canvas/README.md +++ b/src/components/gauss_canvas/README.md @@ -8,9 +8,9 @@ ### 属性 -- `src`: 要高斯模糊的图片URL +- `src`: 要高斯模糊的图片 URL - `sigma`: 高斯模糊程度系数,除了第一次传值时不会有模糊渐变,之后传值时图片模糊会呈渐变,如若不需要动画则调用方法 `showImmediately` 并传值 -- `threshold`: 当 `sigma` 大于该值时,组件采用模糊算法,否则采用降低分辨率的方法模糊图片,默认值为1 +- `threshold`: 当 `sigma` 大于该值时,组件采用模糊算法,否则采用降低分辨率的方法模糊图片,默认值为 1 - `during`: 模糊渐变的最长时间,单位为 `ms`,默认值为 500 - `bezier`: 模糊渐变的贝塞尔系数,接受参数为一个有四个数字元素的数组,默认值为 `[0.19, 1, 0.22, 1]` @@ -18,45 +18,44 @@ 注意,请不要使用跨域图片资源,否则无法转化为 `ImageData` 进行模糊计算 - ```html ``` ```js -import "@star-web-element/gauss" -const canvas = document.querySelector('gauss-canvas'); +import '@star-web-element/gauss' +const canvas = document.querySelector('gauss-canvas') canvas.addEventListener('click', () => { - // 会有模糊渐变 - canvas.sigma ^= 10 + // 会有模糊渐变 + canvas.sigma ^= 10 }) -canvas.addEventListener('mousedown', handleEvent); -canvas.addEventListener('mousemove', handleEvent); -canvas.addEventListener('mouseup', handleEvent); +canvas.addEventListener('mousedown', handleEvent) +canvas.addEventListener('mousemove', handleEvent) +canvas.addEventListener('mouseup', handleEvent) let mouseData = { - start: 0, - moveDistance: 0, + 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; - } + 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 + } } ``` diff --git a/src/components/indicator/indicator-page-deformation.ts b/src/components/indicator/indicator-page-deformation.ts new file mode 100644 index 0000000..defd6ff --- /dev/null +++ b/src/components/indicator/indicator-page-deformation.ts @@ -0,0 +1,134 @@ +import {html, css, LitElement, PropertyValueMap, svg} from 'lit' +import {customElement, property, state} 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 + + @state() width = 22 + (this.total - 1) * (4 * 2 + 8) + @state() viewBox = `0 0 ${this.width} ${4 * 2}` + @state() rectBeginX = (this.index - 1) * (4 * 2 + 8) + + protected shouldUpdate(_changedProperties: PropertyValueMap): 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 + + for (let i = 1; i <= this.total; i++) { + if (i == this.index) { + all.push( + svg`` + ) + 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``) + } + } + + return html` + + ${all} + + ` + } + + static styles = css` + :host { + display: flex; + width: 100%; + margin: auto; + max-height: 50px; + } + :host([column]) { + display: inline-flex; + width: auto; + } + svg { + flex: 1; + } + svg rect, + :host(.light) svg rect { + fill: #ffffff; + fill-opacity: 0.85; + } + svg circle, + :host(.light) svg circle { + fill: #ffffff; + fill-opacity: 0.4; + } + :host(.dark) svg rect { + fill: #1b1b1b; + fill-opacity: 0.7; + } + :host(.dark) svg circle { + fill: #1b1b1b; + fill-opacity: 0.3; + } + ` +} + +declare global { + interface HTMLElementTagNameMap { + 'indicator-page-deformation': IndicatorPageDeformation + } +} diff --git a/src/test/panels/indicators/README.md b/src/test/panels/indicators/README.md new file mode 100644 index 0000000..bee572b --- /dev/null +++ b/src/test/panels/indicators/README.md @@ -0,0 +1,13 @@ +# 指示器 + +## 页面指示器 + +### 页面圆点指示器 + +基本类型 + +### 页面横条指示器 + +圆点+横条,由 svg 组成。 + +TODO: 添加可形变动画。 \ No newline at end of file diff --git a/src/test/panels/indicators/deformation-indicator.ts b/src/test/panels/indicators/deformation-indicator.ts new file mode 100644 index 0000000..464315a --- /dev/null +++ b/src/test/panels/indicators/deformation-indicator.ts @@ -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` +

基本展示

+ + + + + + + + + + +

切换状态

+
+
+ + + + + +
+
+ + + ` + } + + 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 + } +} diff --git a/src/test/panels/indicators/indicators.ts b/src/test/panels/indicators/point-indicator.ts similarity index 96% rename from src/test/panels/indicators/indicators.ts rename to src/test/panels/indicators/point-indicator.ts index ec98a39..56ff1a7 100644 --- a/src/test/panels/indicators/indicators.ts +++ b/src/test/panels/indicators/point-indicator.ts @@ -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 } } diff --git a/src/test/panels/root.ts b/src/test/panels/root.ts index 57fbe06..9dd15fe 100644 --- a/src/test/panels/root.ts +++ b/src/test/panels/root.ts @@ -11,8 +11,9 @@ 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' @@ -243,14 +244,6 @@ export class PanelRoot extends LitElement { href="#confirm" >

- -
+ + +
+ +
+ Date: Tue, 27 Sep 2022 18:39:26 +0800 Subject: [PATCH 20/20] (fix)entered transform state by mistake --- .../indicator/indicator-page-deformation.ts | 67 +++++++++++++------ src/lib/gesture/gesture-detector.ts | 5 +- 2 files changed, 49 insertions(+), 23 deletions(-) diff --git a/src/components/indicator/indicator-page-deformation.ts b/src/components/indicator/indicator-page-deformation.ts index defd6ff..cae3386 100644 --- a/src/components/indicator/indicator-page-deformation.ts +++ b/src/components/indicator/indicator-page-deformation.ts @@ -1,5 +1,5 @@ import {html, css, LitElement, PropertyValueMap, svg} from 'lit' -import {customElement, property, state} from 'lit/decorators.js' +import {customElement, property} from 'lit/decorators.js' @customElement('indicator-page-deformation') export class IndicatorPageDeformation extends LitElement { @@ -10,10 +10,6 @@ export class IndicatorPageDeformation extends LitElement { #firstRender = true - @state() width = 22 + (this.total - 1) * (4 * 2 + 8) - @state() viewBox = `0 0 ${this.width} ${4 * 2}` - @state() rectBeginX = (this.index - 1) * (4 * 2 + 8) - protected shouldUpdate(_changedProperties: PropertyValueMap): boolean { let isShouldUpdate = true @@ -69,12 +65,13 @@ export class IndicatorPageDeformation extends LitElement { 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`` - ) + all.push(svg``) hasRectFlag = true } else { let cx = 0 @@ -86,9 +83,7 @@ export class IndicatorPageDeformation extends LitElement { } return html` - - ${all} - + ${all} ` } @@ -98,6 +93,14 @@ export class IndicatorPageDeformation extends LitElement { 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; @@ -106,23 +109,43 @@ export class IndicatorPageDeformation extends LitElement { svg { flex: 1; } - svg rect, - :host(.light) svg rect { - fill: #ffffff; - fill-opacity: 0.85; + 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); } - svg circle, :host(.light) svg circle { - fill: #ffffff; - fill-opacity: 0.4; + fill: var(--light-indicator-fill); + fill-opacity: var(--light-indicator-fill-circ-opacity); } :host(.dark) svg rect { - fill: #1b1b1b; - fill-opacity: 0.7; + fill: var(--dark-indicator-fill); + fill-opacity: var(--dark-indicator-fill-rect-opacity); } :host(.dark) svg circle { - fill: #1b1b1b; - fill-opacity: 0.3; + 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); + } } ` } diff --git a/src/lib/gesture/gesture-detector.ts b/src/lib/gesture/gesture-detector.ts index b735070..47652cb 100644 --- a/src/lib/gesture/gesture-detector.ts +++ b/src/lib/gesture/gesture-detector.ts @@ -544,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)